Completed
Push — master ( 453450...6f0537 )
by
unknown
37:16
created
apps/files_sharing/lib/ExpireSharesJob.php 2 patches
Indentation   +47 added lines, -47 removed lines patch added patch discarded remove patch
@@ -20,60 +20,60 @@
 block discarded – undo
20 20
  */
21 21
 class ExpireSharesJob extends TimedJob {
22 22
 
23
-	public function __construct(
24
-		ITimeFactory $time,
25
-		private IManager $shareManager,
26
-		private IDBConnection $db,
27
-	) {
28
-		parent::__construct($time);
23
+    public function __construct(
24
+        ITimeFactory $time,
25
+        private IManager $shareManager,
26
+        private IDBConnection $db,
27
+    ) {
28
+        parent::__construct($time);
29 29
 
30
-		// Run once a day
31
-		$this->setInterval(24 * 60 * 60);
32
-		$this->setTimeSensitivity(self::TIME_INSENSITIVE);
33
-	}
30
+        // Run once a day
31
+        $this->setInterval(24 * 60 * 60);
32
+        $this->setTimeSensitivity(self::TIME_INSENSITIVE);
33
+    }
34 34
 
35 35
 
36
-	/**
37
-	 * Makes the background job do its work
38
-	 *
39
-	 * @param array $argument unused argument
40
-	 */
41
-	public function run($argument) {
42
-		//Current time
43
-		$now = new \DateTime();
44
-		$now = $now->format('Y-m-d H:i:s');
36
+    /**
37
+     * Makes the background job do its work
38
+     *
39
+     * @param array $argument unused argument
40
+     */
41
+    public function run($argument) {
42
+        //Current time
43
+        $now = new \DateTime();
44
+        $now = $now->format('Y-m-d H:i:s');
45 45
 
46
-		/*
46
+        /*
47 47
 		 * Expire file link shares only (for now)
48 48
 		 */
49
-		$qb = $this->db->getQueryBuilder();
50
-		$qb->select('id', 'share_type')
51
-			->from('share')
52
-			->where(
53
-				$qb->expr()->andX(
54
-					$qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_LINK, IShare::TYPE_EMAIL], IQueryBuilder::PARAM_INT_ARRAY)),
55
-					$qb->expr()->lte('expiration', $qb->expr()->literal($now)),
56
-					$qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY))
57
-				)
58
-			);
49
+        $qb = $this->db->getQueryBuilder();
50
+        $qb->select('id', 'share_type')
51
+            ->from('share')
52
+            ->where(
53
+                $qb->expr()->andX(
54
+                    $qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_LINK, IShare::TYPE_EMAIL], IQueryBuilder::PARAM_INT_ARRAY)),
55
+                    $qb->expr()->lte('expiration', $qb->expr()->literal($now)),
56
+                    $qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY))
57
+                )
58
+            );
59 59
 
60
-		$shares = $qb->executeQuery();
61
-		while ($share = $shares->fetchAssociative()) {
62
-			if ((int)$share['share_type'] === IShare::TYPE_LINK) {
63
-				$id = 'ocinternal';
64
-			} elseif ((int)$share['share_type'] === IShare::TYPE_EMAIL) {
65
-				$id = 'ocMailShare';
66
-			}
60
+        $shares = $qb->executeQuery();
61
+        while ($share = $shares->fetchAssociative()) {
62
+            if ((int)$share['share_type'] === IShare::TYPE_LINK) {
63
+                $id = 'ocinternal';
64
+            } elseif ((int)$share['share_type'] === IShare::TYPE_EMAIL) {
65
+                $id = 'ocMailShare';
66
+            }
67 67
 
68
-			$id .= ':' . $share['id'];
68
+            $id .= ':' . $share['id'];
69 69
 
70
-			try {
71
-				$share = $this->shareManager->getShareById($id);
72
-				$this->shareManager->deleteShare($share);
73
-			} catch (ShareNotFound $e) {
74
-				// Normally the share gets automatically expired on fetching it
75
-			}
76
-		}
77
-		$shares->closeCursor();
78
-	}
70
+            try {
71
+                $share = $this->shareManager->getShareById($id);
72
+                $this->shareManager->deleteShare($share);
73
+            } catch (ShareNotFound $e) {
74
+                // Normally the share gets automatically expired on fetching it
75
+            }
76
+        }
77
+        $shares->closeCursor();
78
+    }
79 79
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -59,13 +59,13 @@
 block discarded – undo
59 59
 
60 60
 		$shares = $qb->executeQuery();
61 61
 		while ($share = $shares->fetchAssociative()) {
62
-			if ((int)$share['share_type'] === IShare::TYPE_LINK) {
62
+			if ((int) $share['share_type'] === IShare::TYPE_LINK) {
63 63
 				$id = 'ocinternal';
64
-			} elseif ((int)$share['share_type'] === IShare::TYPE_EMAIL) {
64
+			} elseif ((int) $share['share_type'] === IShare::TYPE_EMAIL) {
65 65
 				$id = 'ocMailShare';
66 66
 			}
67 67
 
68
-			$id .= ':' . $share['id'];
68
+			$id .= ':'.$share['id'];
69 69
 
70 70
 			try {
71 71
 				$share = $this->shareManager->getShareById($id);
Please login to merge, or discard this patch.
apps/files_sharing/lib/ShareBackend/File.php 1 patch
Indentation   +206 added lines, -206 removed lines patch added patch discarded remove patch
@@ -19,210 +19,210 @@
 block discarded – undo
19 19
 use Psr\Log\LoggerInterface;
20 20
 
21 21
 class File implements Share_Backend_File_Dependent {
22
-	public const FORMAT_SHARED_STORAGE = 0;
23
-	public const FORMAT_GET_FOLDER_CONTENTS = 1;
24
-	public const FORMAT_FILE_APP_ROOT = 2;
25
-	public const FORMAT_OPENDIR = 3;
26
-	public const FORMAT_GET_ALL = 4;
27
-	public const FORMAT_PERMISSIONS = 5;
28
-	public const FORMAT_TARGET_NAMES = 6;
29
-
30
-	private $path;
31
-
32
-	public function __construct(
33
-		private ?FederatedShareProvider $federatedShareProvider = null,
34
-	) {
35
-		if ($federatedShareProvider) {
36
-			$this->federatedShareProvider = $federatedShareProvider;
37
-		} else {
38
-			$this->federatedShareProvider = Server::get(FederatedShareProvider::class);
39
-		}
40
-	}
41
-
42
-	public function isValidSource($itemSource, $uidOwner) {
43
-		try {
44
-			$path = Filesystem::getPath($itemSource);
45
-			// FIXME: attributes should not be set here,
46
-			// keeping this pattern for now to avoid unexpected
47
-			// regressions
48
-			$this->path = Filesystem::normalizePath(basename($path));
49
-			return true;
50
-		} catch (NotFoundException $e) {
51
-			return false;
52
-		}
53
-	}
54
-
55
-	public function getFilePath($itemSource, $uidOwner) {
56
-		if (isset($this->path)) {
57
-			$path = $this->path;
58
-			$this->path = null;
59
-			return $path;
60
-		} else {
61
-			try {
62
-				$path = Filesystem::getPath($itemSource);
63
-				return $path;
64
-			} catch (NotFoundException $e) {
65
-				return false;
66
-			}
67
-		}
68
-	}
69
-
70
-	/**
71
-	 * create unique target
72
-	 *
73
-	 * @param string $itemSource
74
-	 * @param string $shareWith
75
-	 * @return string
76
-	 */
77
-	public function generateTarget($itemSource, $shareWith) {
78
-		$shareFolder = Helper::getShareFolder();
79
-		$target = Filesystem::normalizePath($shareFolder . '/' . basename($itemSource));
80
-
81
-		Filesystem::initMountPoints($shareWith);
82
-		$view = new View('/' . $shareWith . '/files');
83
-
84
-		if (!$view->is_dir($shareFolder)) {
85
-			$dir = '';
86
-			$subdirs = explode('/', $shareFolder);
87
-			foreach ($subdirs as $subdir) {
88
-				$dir = $dir . '/' . $subdir;
89
-				if (!$view->is_dir($dir)) {
90
-					$view->mkdir($dir);
91
-				}
92
-			}
93
-		}
94
-
95
-		return Helper::generateUniqueTarget($target, $view);
96
-	}
97
-
98
-	public function formatItems($items, $format, $parameters = null) {
99
-		if ($format === self::FORMAT_SHARED_STORAGE) {
100
-			// Only 1 item should come through for this format call
101
-			$item = array_shift($items);
102
-			return [
103
-				'parent' => $item['parent'],
104
-				'path' => $item['path'],
105
-				'storage' => $item['storage'],
106
-				'permissions' => $item['permissions'],
107
-				'uid_owner' => $item['uid_owner'],
108
-			];
109
-		} elseif ($format === self::FORMAT_GET_FOLDER_CONTENTS) {
110
-			$files = [];
111
-			foreach ($items as $item) {
112
-				$file = [];
113
-				$file['fileid'] = $item['file_source'];
114
-				$file['storage'] = $item['storage'];
115
-				$file['path'] = $item['file_target'];
116
-				$file['parent'] = $item['file_parent'];
117
-				$file['name'] = basename($item['file_target']);
118
-				$file['mimetype'] = $item['mimetype'];
119
-				$file['mimepart'] = $item['mimepart'];
120
-				$file['mtime'] = $item['mtime'];
121
-				$file['encrypted'] = $item['encrypted'];
122
-				$file['etag'] = $item['etag'];
123
-				$file['uid_owner'] = $item['uid_owner'];
124
-				$file['displayname_owner'] = $item['displayname_owner'];
125
-
126
-				$storage = Filesystem::getStorage('/');
127
-				$cache = $storage->getCache();
128
-				$file['size'] = $item['size'];
129
-				$files[] = $file;
130
-			}
131
-			return $files;
132
-		} elseif ($format === self::FORMAT_OPENDIR) {
133
-			$files = [];
134
-			foreach ($items as $item) {
135
-				$files[] = basename($item['file_target']);
136
-			}
137
-			return $files;
138
-		} elseif ($format === self::FORMAT_GET_ALL) {
139
-			$ids = [];
140
-			foreach ($items as $item) {
141
-				$ids[] = $item['file_source'];
142
-			}
143
-			return $ids;
144
-		} elseif ($format === self::FORMAT_PERMISSIONS) {
145
-			$filePermissions = [];
146
-			foreach ($items as $item) {
147
-				$filePermissions[$item['file_source']] = $item['permissions'];
148
-			}
149
-			return $filePermissions;
150
-		} elseif ($format === self::FORMAT_TARGET_NAMES) {
151
-			$targets = [];
152
-			foreach ($items as $item) {
153
-				$targets[] = $item['file_target'];
154
-			}
155
-			return $targets;
156
-		}
157
-		return [];
158
-	}
159
-
160
-	/**
161
-	 * check if server2server share is enabled
162
-	 *
163
-	 * @param int $shareType
164
-	 * @return boolean
165
-	 */
166
-	public function isShareTypeAllowed($shareType) {
167
-		if ($shareType === IShare::TYPE_REMOTE) {
168
-			return $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
169
-		}
170
-
171
-		if ($shareType === IShare::TYPE_REMOTE_GROUP) {
172
-			return $this->federatedShareProvider->isOutgoingServer2serverGroupShareEnabled();
173
-		}
174
-
175
-		return true;
176
-	}
177
-
178
-	/**
179
-	 * resolve reshares to return the correct source item
180
-	 * @param array $source
181
-	 * @return array source item
182
-	 */
183
-	protected static function resolveReshares($source) {
184
-		if (isset($source['parent'])) {
185
-			$parent = $source['parent'];
186
-			while (isset($parent)) {
187
-				$qb = Server::get(IDBConnection::class)->getQueryBuilder();
188
-				$qb->select('parent', 'uid_owner')
189
-					->from('share')
190
-					->where(
191
-						$qb->expr()->eq('id', $qb->createNamedParameter($parent))
192
-					);
193
-				$result = $qb->executeQuery();
194
-				$item = $result->fetchAssociative();
195
-				$result->closeCursor();
196
-				if (isset($item['parent'])) {
197
-					$parent = $item['parent'];
198
-				} else {
199
-					$fileOwner = $item['uid_owner'];
200
-					break;
201
-				}
202
-			}
203
-		} else {
204
-			$fileOwner = $source['uid_owner'];
205
-		}
206
-		if (isset($fileOwner)) {
207
-			$source['fileOwner'] = $fileOwner;
208
-		} else {
209
-			Server::get(LoggerInterface::class)->error('No owner found for reshare', ['app' => 'files_sharing']);
210
-		}
211
-
212
-		return $source;
213
-	}
214
-
215
-	/**
216
-	 * @param string $target
217
-	 * @param array $share
218
-	 * @return array|false source item
219
-	 */
220
-	public static function getSource($target, $share) {
221
-		if ($share['item_type'] === 'folder' && $target !== '') {
222
-			// note: in case of ext storage mount points the path might be empty
223
-			// which would cause a leading slash to appear
224
-			$share['path'] = ltrim($share['path'] . '/' . $target, '/');
225
-		}
226
-		return self::resolveReshares($share);
227
-	}
22
+    public const FORMAT_SHARED_STORAGE = 0;
23
+    public const FORMAT_GET_FOLDER_CONTENTS = 1;
24
+    public const FORMAT_FILE_APP_ROOT = 2;
25
+    public const FORMAT_OPENDIR = 3;
26
+    public const FORMAT_GET_ALL = 4;
27
+    public const FORMAT_PERMISSIONS = 5;
28
+    public const FORMAT_TARGET_NAMES = 6;
29
+
30
+    private $path;
31
+
32
+    public function __construct(
33
+        private ?FederatedShareProvider $federatedShareProvider = null,
34
+    ) {
35
+        if ($federatedShareProvider) {
36
+            $this->federatedShareProvider = $federatedShareProvider;
37
+        } else {
38
+            $this->federatedShareProvider = Server::get(FederatedShareProvider::class);
39
+        }
40
+    }
41
+
42
+    public function isValidSource($itemSource, $uidOwner) {
43
+        try {
44
+            $path = Filesystem::getPath($itemSource);
45
+            // FIXME: attributes should not be set here,
46
+            // keeping this pattern for now to avoid unexpected
47
+            // regressions
48
+            $this->path = Filesystem::normalizePath(basename($path));
49
+            return true;
50
+        } catch (NotFoundException $e) {
51
+            return false;
52
+        }
53
+    }
54
+
55
+    public function getFilePath($itemSource, $uidOwner) {
56
+        if (isset($this->path)) {
57
+            $path = $this->path;
58
+            $this->path = null;
59
+            return $path;
60
+        } else {
61
+            try {
62
+                $path = Filesystem::getPath($itemSource);
63
+                return $path;
64
+            } catch (NotFoundException $e) {
65
+                return false;
66
+            }
67
+        }
68
+    }
69
+
70
+    /**
71
+     * create unique target
72
+     *
73
+     * @param string $itemSource
74
+     * @param string $shareWith
75
+     * @return string
76
+     */
77
+    public function generateTarget($itemSource, $shareWith) {
78
+        $shareFolder = Helper::getShareFolder();
79
+        $target = Filesystem::normalizePath($shareFolder . '/' . basename($itemSource));
80
+
81
+        Filesystem::initMountPoints($shareWith);
82
+        $view = new View('/' . $shareWith . '/files');
83
+
84
+        if (!$view->is_dir($shareFolder)) {
85
+            $dir = '';
86
+            $subdirs = explode('/', $shareFolder);
87
+            foreach ($subdirs as $subdir) {
88
+                $dir = $dir . '/' . $subdir;
89
+                if (!$view->is_dir($dir)) {
90
+                    $view->mkdir($dir);
91
+                }
92
+            }
93
+        }
94
+
95
+        return Helper::generateUniqueTarget($target, $view);
96
+    }
97
+
98
+    public function formatItems($items, $format, $parameters = null) {
99
+        if ($format === self::FORMAT_SHARED_STORAGE) {
100
+            // Only 1 item should come through for this format call
101
+            $item = array_shift($items);
102
+            return [
103
+                'parent' => $item['parent'],
104
+                'path' => $item['path'],
105
+                'storage' => $item['storage'],
106
+                'permissions' => $item['permissions'],
107
+                'uid_owner' => $item['uid_owner'],
108
+            ];
109
+        } elseif ($format === self::FORMAT_GET_FOLDER_CONTENTS) {
110
+            $files = [];
111
+            foreach ($items as $item) {
112
+                $file = [];
113
+                $file['fileid'] = $item['file_source'];
114
+                $file['storage'] = $item['storage'];
115
+                $file['path'] = $item['file_target'];
116
+                $file['parent'] = $item['file_parent'];
117
+                $file['name'] = basename($item['file_target']);
118
+                $file['mimetype'] = $item['mimetype'];
119
+                $file['mimepart'] = $item['mimepart'];
120
+                $file['mtime'] = $item['mtime'];
121
+                $file['encrypted'] = $item['encrypted'];
122
+                $file['etag'] = $item['etag'];
123
+                $file['uid_owner'] = $item['uid_owner'];
124
+                $file['displayname_owner'] = $item['displayname_owner'];
125
+
126
+                $storage = Filesystem::getStorage('/');
127
+                $cache = $storage->getCache();
128
+                $file['size'] = $item['size'];
129
+                $files[] = $file;
130
+            }
131
+            return $files;
132
+        } elseif ($format === self::FORMAT_OPENDIR) {
133
+            $files = [];
134
+            foreach ($items as $item) {
135
+                $files[] = basename($item['file_target']);
136
+            }
137
+            return $files;
138
+        } elseif ($format === self::FORMAT_GET_ALL) {
139
+            $ids = [];
140
+            foreach ($items as $item) {
141
+                $ids[] = $item['file_source'];
142
+            }
143
+            return $ids;
144
+        } elseif ($format === self::FORMAT_PERMISSIONS) {
145
+            $filePermissions = [];
146
+            foreach ($items as $item) {
147
+                $filePermissions[$item['file_source']] = $item['permissions'];
148
+            }
149
+            return $filePermissions;
150
+        } elseif ($format === self::FORMAT_TARGET_NAMES) {
151
+            $targets = [];
152
+            foreach ($items as $item) {
153
+                $targets[] = $item['file_target'];
154
+            }
155
+            return $targets;
156
+        }
157
+        return [];
158
+    }
159
+
160
+    /**
161
+     * check if server2server share is enabled
162
+     *
163
+     * @param int $shareType
164
+     * @return boolean
165
+     */
166
+    public function isShareTypeAllowed($shareType) {
167
+        if ($shareType === IShare::TYPE_REMOTE) {
168
+            return $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
169
+        }
170
+
171
+        if ($shareType === IShare::TYPE_REMOTE_GROUP) {
172
+            return $this->federatedShareProvider->isOutgoingServer2serverGroupShareEnabled();
173
+        }
174
+
175
+        return true;
176
+    }
177
+
178
+    /**
179
+     * resolve reshares to return the correct source item
180
+     * @param array $source
181
+     * @return array source item
182
+     */
183
+    protected static function resolveReshares($source) {
184
+        if (isset($source['parent'])) {
185
+            $parent = $source['parent'];
186
+            while (isset($parent)) {
187
+                $qb = Server::get(IDBConnection::class)->getQueryBuilder();
188
+                $qb->select('parent', 'uid_owner')
189
+                    ->from('share')
190
+                    ->where(
191
+                        $qb->expr()->eq('id', $qb->createNamedParameter($parent))
192
+                    );
193
+                $result = $qb->executeQuery();
194
+                $item = $result->fetchAssociative();
195
+                $result->closeCursor();
196
+                if (isset($item['parent'])) {
197
+                    $parent = $item['parent'];
198
+                } else {
199
+                    $fileOwner = $item['uid_owner'];
200
+                    break;
201
+                }
202
+            }
203
+        } else {
204
+            $fileOwner = $source['uid_owner'];
205
+        }
206
+        if (isset($fileOwner)) {
207
+            $source['fileOwner'] = $fileOwner;
208
+        } else {
209
+            Server::get(LoggerInterface::class)->error('No owner found for reshare', ['app' => 'files_sharing']);
210
+        }
211
+
212
+        return $source;
213
+    }
214
+
215
+    /**
216
+     * @param string $target
217
+     * @param array $share
218
+     * @return array|false source item
219
+     */
220
+    public static function getSource($target, $share) {
221
+        if ($share['item_type'] === 'folder' && $target !== '') {
222
+            // note: in case of ext storage mount points the path might be empty
223
+            // which would cause a leading slash to appear
224
+            $share['path'] = ltrim($share['path'] . '/' . $target, '/');
225
+        }
226
+        return self::resolveReshares($share);
227
+    }
228 228
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/ShareBackend/Folder.php 2 patches
Indentation   +46 added lines, -46 removed lines patch added patch discarded remove patch
@@ -12,50 +12,50 @@
 block discarded – undo
12 12
 use OCP\Share_Backend_Collection;
13 13
 
14 14
 class Folder extends File implements Share_Backend_Collection {
15
-	public function getChildren($itemSource): array {
16
-		$children = [];
17
-		$parents = [$itemSource];
18
-
19
-		$qb = Server::get(IDBConnection::class)->getQueryBuilder();
20
-		$qb->select('id')
21
-			->from('mimetypes')
22
-			->where(
23
-				$qb->expr()->eq('mimetype', $qb->createNamedParameter('httpd/unix-directory'))
24
-			);
25
-		$result = $qb->executeQuery();
26
-
27
-		if (($row = $result->fetchAssociative()) !== false) {
28
-			$mimetype = (int)$row['id'];
29
-		} else {
30
-			$mimetype = -1;
31
-		}
32
-		$result->closeCursor();
33
-
34
-		while (!empty($parents)) {
35
-			$qb = Server::get(IDBConnection::class)->getQueryBuilder();
36
-
37
-			$parents = array_map(function ($parent) use ($qb) {
38
-				return $qb->createNamedParameter($parent);
39
-			}, $parents);
40
-
41
-			$qb->select('`fileid', 'name', '`mimetype')
42
-				->from('filecache')
43
-				->where(
44
-					$qb->expr()->in('parent', $parents)
45
-				);
46
-
47
-			$result = $qb->executeQuery();
48
-
49
-			$parents = [];
50
-			foreach ($result->iterateAssociative() as $file) {
51
-				$children[] = ['source' => $file['fileid'], 'file_path' => $file['name']];
52
-				// If a child folder is found look inside it
53
-				if ((int)$file['mimetype'] === $mimetype) {
54
-					$parents[] = $file['fileid'];
55
-				}
56
-			}
57
-			$result->closeCursor();
58
-		}
59
-		return $children;
60
-	}
15
+    public function getChildren($itemSource): array {
16
+        $children = [];
17
+        $parents = [$itemSource];
18
+
19
+        $qb = Server::get(IDBConnection::class)->getQueryBuilder();
20
+        $qb->select('id')
21
+            ->from('mimetypes')
22
+            ->where(
23
+                $qb->expr()->eq('mimetype', $qb->createNamedParameter('httpd/unix-directory'))
24
+            );
25
+        $result = $qb->executeQuery();
26
+
27
+        if (($row = $result->fetchAssociative()) !== false) {
28
+            $mimetype = (int)$row['id'];
29
+        } else {
30
+            $mimetype = -1;
31
+        }
32
+        $result->closeCursor();
33
+
34
+        while (!empty($parents)) {
35
+            $qb = Server::get(IDBConnection::class)->getQueryBuilder();
36
+
37
+            $parents = array_map(function ($parent) use ($qb) {
38
+                return $qb->createNamedParameter($parent);
39
+            }, $parents);
40
+
41
+            $qb->select('`fileid', 'name', '`mimetype')
42
+                ->from('filecache')
43
+                ->where(
44
+                    $qb->expr()->in('parent', $parents)
45
+                );
46
+
47
+            $result = $qb->executeQuery();
48
+
49
+            $parents = [];
50
+            foreach ($result->iterateAssociative() as $file) {
51
+                $children[] = ['source' => $file['fileid'], 'file_path' => $file['name']];
52
+                // If a child folder is found look inside it
53
+                if ((int)$file['mimetype'] === $mimetype) {
54
+                    $parents[] = $file['fileid'];
55
+                }
56
+            }
57
+            $result->closeCursor();
58
+        }
59
+        return $children;
60
+    }
61 61
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -25,7 +25,7 @@  discard block
 block discarded – undo
25 25
 		$result = $qb->executeQuery();
26 26
 
27 27
 		if (($row = $result->fetchAssociative()) !== false) {
28
-			$mimetype = (int)$row['id'];
28
+			$mimetype = (int) $row['id'];
29 29
 		} else {
30 30
 			$mimetype = -1;
31 31
 		}
@@ -34,7 +34,7 @@  discard block
 block discarded – undo
34 34
 		while (!empty($parents)) {
35 35
 			$qb = Server::get(IDBConnection::class)->getQueryBuilder();
36 36
 
37
-			$parents = array_map(function ($parent) use ($qb) {
37
+			$parents = array_map(function($parent) use ($qb) {
38 38
 				return $qb->createNamedParameter($parent);
39 39
 			}, $parents);
40 40
 
@@ -50,7 +50,7 @@  discard block
 block discarded – undo
50 50
 			foreach ($result->iterateAssociative() as $file) {
51 51
 				$children[] = ['source' => $file['fileid'], 'file_path' => $file['name']];
52 52
 				// If a child folder is found look inside it
53
-				if ((int)$file['mimetype'] === $mimetype) {
53
+				if ((int) $file['mimetype'] === $mimetype) {
54 54
 					$parents[] = $file['fileid'];
55 55
 				}
56 56
 			}
Please login to merge, or discard this patch.
lib/private/DB/ArrayResult.php 2 patches
Indentation   +116 added lines, -116 removed lines patch added patch discarded remove patch
@@ -16,120 +16,120 @@
 block discarded – undo
16 16
  * Wrap an array or rows into a result interface
17 17
  */
18 18
 class ArrayResult implements IResult {
19
-	protected int $count;
20
-
21
-	public function __construct(
22
-		/** @var array<string, mixed> $rows */
23
-		protected array $rows,
24
-	) {
25
-		$this->count = count($this->rows);
26
-	}
27
-
28
-	#[Override]
29
-	public function closeCursor(): bool {
30
-		// noop
31
-		return true;
32
-	}
33
-
34
-	#[Override]
35
-	public function fetch(int $fetchMode = PDO::FETCH_ASSOC) {
36
-		$row = array_shift($this->rows);
37
-		if (!$row) {
38
-			return false;
39
-		}
40
-		return match ($fetchMode) {
41
-			PDO::FETCH_ASSOC => $row,
42
-			PDO::FETCH_NUM => array_values($row),
43
-			PDO::FETCH_COLUMN => current($row),
44
-			default => throw new \InvalidArgumentException('Fetch mode not supported for array result'),
45
-		};
46
-
47
-	}
48
-
49
-	#[Override]
50
-	public function fetchAll(int $fetchMode = PDO::FETCH_ASSOC): array {
51
-		return match ($fetchMode) {
52
-			PDO::FETCH_ASSOC => $this->rows,
53
-			PDO::FETCH_NUM => array_map(static fn (array $row): array => array_values($row), $this->rows),
54
-			PDO::FETCH_COLUMN => array_map(static fn (array $row): mixed => current($row), $this->rows),
55
-			default => throw new \InvalidArgumentException('Fetch mode not supported for array result'),
56
-		};
57
-	}
58
-
59
-	#[Override]
60
-	public function fetchColumn() {
61
-		return $this->fetchOne();
62
-	}
63
-
64
-	#[Override]
65
-	public function fetchOne() {
66
-		$row = $this->fetch();
67
-		if ($row) {
68
-			return current($row);
69
-		} else {
70
-			return false;
71
-		}
72
-	}
73
-
74
-	#[Override]
75
-	public function fetchAssociative(): array|false {
76
-		$row = $this->fetch();
77
-		if ($row) {
78
-			/** @var array<string, mixed> $row */
79
-			return $row;
80
-		} else {
81
-			return false;
82
-		}
83
-	}
84
-
85
-	#[Override]
86
-	public function fetchNumeric(): array|false {
87
-		$row = $this->fetch(PDO::FETCH_NUM);
88
-		if ($row) {
89
-			/** @var list<mixed> $row */
90
-			return $row;
91
-		} else {
92
-			return false;
93
-		}
94
-	}
95
-
96
-	#[Override]
97
-	public function fetchAllNumeric(): array {
98
-		/** @var list<list<mixed>> $result */
99
-		$result = $this->fetchAll(PDO::FETCH_NUM);
100
-		return $result;
101
-	}
102
-
103
-	#[Override]
104
-	public function fetchAllAssociative(): array {
105
-		/** @var list<array<string,mixed>> $result */
106
-		$result = $this->fetchAll();
107
-		return $result;
108
-	}
109
-
110
-	#[Override]
111
-	public function fetchFirstColumn(): array {
112
-		/** @var list<mixed> $result */
113
-		$result = $this->fetchAll(PDO::FETCH_COLUMN);
114
-		return $result;
115
-	}
116
-
117
-	#[Override]
118
-	public function rowCount(): int {
119
-		return $this->count;
120
-	}
121
-
122
-	#[Override]
123
-	public function iterateNumeric(): \Traversable {
124
-		while (($row = $this->fetchNumeric()) !== false) {
125
-			yield $row;
126
-		}
127
-	}
128
-
129
-	#[Override]
130
-	public function iterateAssociative(): \Traversable {
131
-		while (($row = $this->fetchAssociative()) !== false) {
132
-			yield $row;
133
-		}
134
-	}
19
+    protected int $count;
20
+
21
+    public function __construct(
22
+        /** @var array<string, mixed> $rows */
23
+        protected array $rows,
24
+    ) {
25
+        $this->count = count($this->rows);
26
+    }
27
+
28
+    #[Override]
29
+    public function closeCursor(): bool {
30
+        // noop
31
+        return true;
32
+    }
33
+
34
+    #[Override]
35
+    public function fetch(int $fetchMode = PDO::FETCH_ASSOC) {
36
+        $row = array_shift($this->rows);
37
+        if (!$row) {
38
+            return false;
39
+        }
40
+        return match ($fetchMode) {
41
+            PDO::FETCH_ASSOC => $row,
42
+            PDO::FETCH_NUM => array_values($row),
43
+            PDO::FETCH_COLUMN => current($row),
44
+            default => throw new \InvalidArgumentException('Fetch mode not supported for array result'),
45
+        };
46
+
47
+    }
48
+
49
+    #[Override]
50
+    public function fetchAll(int $fetchMode = PDO::FETCH_ASSOC): array {
51
+        return match ($fetchMode) {
52
+            PDO::FETCH_ASSOC => $this->rows,
53
+            PDO::FETCH_NUM => array_map(static fn (array $row): array => array_values($row), $this->rows),
54
+            PDO::FETCH_COLUMN => array_map(static fn (array $row): mixed => current($row), $this->rows),
55
+            default => throw new \InvalidArgumentException('Fetch mode not supported for array result'),
56
+        };
57
+    }
58
+
59
+    #[Override]
60
+    public function fetchColumn() {
61
+        return $this->fetchOne();
62
+    }
63
+
64
+    #[Override]
65
+    public function fetchOne() {
66
+        $row = $this->fetch();
67
+        if ($row) {
68
+            return current($row);
69
+        } else {
70
+            return false;
71
+        }
72
+    }
73
+
74
+    #[Override]
75
+    public function fetchAssociative(): array|false {
76
+        $row = $this->fetch();
77
+        if ($row) {
78
+            /** @var array<string, mixed> $row */
79
+            return $row;
80
+        } else {
81
+            return false;
82
+        }
83
+    }
84
+
85
+    #[Override]
86
+    public function fetchNumeric(): array|false {
87
+        $row = $this->fetch(PDO::FETCH_NUM);
88
+        if ($row) {
89
+            /** @var list<mixed> $row */
90
+            return $row;
91
+        } else {
92
+            return false;
93
+        }
94
+    }
95
+
96
+    #[Override]
97
+    public function fetchAllNumeric(): array {
98
+        /** @var list<list<mixed>> $result */
99
+        $result = $this->fetchAll(PDO::FETCH_NUM);
100
+        return $result;
101
+    }
102
+
103
+    #[Override]
104
+    public function fetchAllAssociative(): array {
105
+        /** @var list<array<string,mixed>> $result */
106
+        $result = $this->fetchAll();
107
+        return $result;
108
+    }
109
+
110
+    #[Override]
111
+    public function fetchFirstColumn(): array {
112
+        /** @var list<mixed> $result */
113
+        $result = $this->fetchAll(PDO::FETCH_COLUMN);
114
+        return $result;
115
+    }
116
+
117
+    #[Override]
118
+    public function rowCount(): int {
119
+        return $this->count;
120
+    }
121
+
122
+    #[Override]
123
+    public function iterateNumeric(): \Traversable {
124
+        while (($row = $this->fetchNumeric()) !== false) {
125
+            yield $row;
126
+        }
127
+    }
128
+
129
+    #[Override]
130
+    public function iterateAssociative(): \Traversable {
131
+        while (($row = $this->fetchAssociative()) !== false) {
132
+            yield $row;
133
+        }
134
+    }
135 135
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -72,7 +72,7 @@  discard block
 block discarded – undo
72 72
 	}
73 73
 
74 74
 	#[Override]
75
-	public function fetchAssociative(): array|false {
75
+	public function fetchAssociative(): array | false {
76 76
 		$row = $this->fetch();
77 77
 		if ($row) {
78 78
 			/** @var array<string, mixed> $row */
@@ -83,7 +83,7 @@  discard block
 block discarded – undo
83 83
 	}
84 84
 
85 85
 	#[Override]
86
-	public function fetchNumeric(): array|false {
86
+	public function fetchNumeric(): array | false {
87 87
 		$row = $this->fetch(PDO::FETCH_NUM);
88 88
 		if ($row) {
89 89
 			/** @var list<mixed> $row */
Please login to merge, or discard this patch.
lib/private/DB/ResultAdapter.php 2 patches
Indentation   +81 added lines, -81 removed lines patch added patch discarded remove patch
@@ -17,85 +17,85 @@
 block discarded – undo
17 17
  * Adapts DBAL 2.6 API for DBAL 3.x for backwards compatibility of a leaked type
18 18
  */
19 19
 class ResultAdapter implements IResult {
20
-	public function __construct(
21
-		private readonly Result $inner,
22
-	) {
23
-	}
24
-
25
-	#[Override]
26
-	public function closeCursor(): bool {
27
-		$this->inner->free();
28
-
29
-		return true;
30
-	}
31
-
32
-	#[Override]
33
-	public function fetch(int $fetchMode = PDO::FETCH_ASSOC) {
34
-		return match ($fetchMode) {
35
-			PDO::FETCH_ASSOC => $this->inner->fetchAssociative(),
36
-			PDO::FETCH_NUM => $this->inner->fetchNumeric(),
37
-			PDO::FETCH_COLUMN => $this->inner->fetchOne(),
38
-			default => throw new \Exception('Fetch mode needs to be assoc, num or column.'),
39
-		};
40
-	}
41
-
42
-	#[Override]
43
-	public function fetchAssociative(): array|false {
44
-		return $this->inner->fetchAssociative();
45
-	}
46
-
47
-	#[Override]
48
-	public function fetchNumeric(): array|false {
49
-		return $this->inner->fetchNumeric();
50
-	}
51
-
52
-	#[Override]
53
-	public function fetchOne(): mixed {
54
-		return $this->inner->fetchOne();
55
-	}
56
-
57
-	#[Override]
58
-	public function fetchAll(int $fetchMode = PDO::FETCH_ASSOC): array {
59
-		return match ($fetchMode) {
60
-			PDO::FETCH_ASSOC => $this->inner->fetchAllAssociative(),
61
-			PDO::FETCH_NUM => $this->inner->fetchAllNumeric(),
62
-			PDO::FETCH_COLUMN => $this->inner->fetchFirstColumn(),
63
-			default => throw new \Exception('Fetch mode needs to be assoc, num or column.'),
64
-		};
65
-	}
66
-
67
-	#[Override]
68
-	public function fetchColumn($columnIndex = 0) {
69
-		return $this->inner->fetchOne();
70
-	}
71
-
72
-	#[Override]
73
-	public function rowCount(): int {
74
-		return $this->inner->rowCount();
75
-	}
76
-
77
-	#[Override]
78
-	public function fetchAllAssociative(): array {
79
-		return $this->inner->fetchAllAssociative();
80
-	}
81
-
82
-	#[Override]
83
-	public function fetchAllNumeric(): array {
84
-		return $this->inner->fetchAllNumeric();
85
-	}
86
-
87
-	#[Override]
88
-	public function fetchFirstColumn(): array {
89
-		return $this->inner->fetchFirstColumn();
90
-	}
91
-
92
-	#[Override]
93
-	public function iterateNumeric(): \Traversable {
94
-		yield from $this->inner->iterateNumeric();
95
-	}
96
-
97
-	#[Override]
98
-	public function iterateAssociative(): \Traversable {
99
-		yield from $this->inner->iterateAssociative();
100
-	}
20
+    public function __construct(
21
+        private readonly Result $inner,
22
+    ) {
23
+    }
24
+
25
+    #[Override]
26
+    public function closeCursor(): bool {
27
+        $this->inner->free();
28
+
29
+        return true;
30
+    }
31
+
32
+    #[Override]
33
+    public function fetch(int $fetchMode = PDO::FETCH_ASSOC) {
34
+        return match ($fetchMode) {
35
+            PDO::FETCH_ASSOC => $this->inner->fetchAssociative(),
36
+            PDO::FETCH_NUM => $this->inner->fetchNumeric(),
37
+            PDO::FETCH_COLUMN => $this->inner->fetchOne(),
38
+            default => throw new \Exception('Fetch mode needs to be assoc, num or column.'),
39
+        };
40
+    }
41
+
42
+    #[Override]
43
+    public function fetchAssociative(): array|false {
44
+        return $this->inner->fetchAssociative();
45
+    }
46
+
47
+    #[Override]
48
+    public function fetchNumeric(): array|false {
49
+        return $this->inner->fetchNumeric();
50
+    }
51
+
52
+    #[Override]
53
+    public function fetchOne(): mixed {
54
+        return $this->inner->fetchOne();
55
+    }
56
+
57
+    #[Override]
58
+    public function fetchAll(int $fetchMode = PDO::FETCH_ASSOC): array {
59
+        return match ($fetchMode) {
60
+            PDO::FETCH_ASSOC => $this->inner->fetchAllAssociative(),
61
+            PDO::FETCH_NUM => $this->inner->fetchAllNumeric(),
62
+            PDO::FETCH_COLUMN => $this->inner->fetchFirstColumn(),
63
+            default => throw new \Exception('Fetch mode needs to be assoc, num or column.'),
64
+        };
65
+    }
66
+
67
+    #[Override]
68
+    public function fetchColumn($columnIndex = 0) {
69
+        return $this->inner->fetchOne();
70
+    }
71
+
72
+    #[Override]
73
+    public function rowCount(): int {
74
+        return $this->inner->rowCount();
75
+    }
76
+
77
+    #[Override]
78
+    public function fetchAllAssociative(): array {
79
+        return $this->inner->fetchAllAssociative();
80
+    }
81
+
82
+    #[Override]
83
+    public function fetchAllNumeric(): array {
84
+        return $this->inner->fetchAllNumeric();
85
+    }
86
+
87
+    #[Override]
88
+    public function fetchFirstColumn(): array {
89
+        return $this->inner->fetchFirstColumn();
90
+    }
91
+
92
+    #[Override]
93
+    public function iterateNumeric(): \Traversable {
94
+        yield from $this->inner->iterateNumeric();
95
+    }
96
+
97
+    #[Override]
98
+    public function iterateAssociative(): \Traversable {
99
+        yield from $this->inner->iterateAssociative();
100
+    }
101 101
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -40,12 +40,12 @@
 block discarded – undo
40 40
 	}
41 41
 
42 42
 	#[Override]
43
-	public function fetchAssociative(): array|false {
43
+	public function fetchAssociative(): array | false {
44 44
 		return $this->inner->fetchAssociative();
45 45
 	}
46 46
 
47 47
 	#[Override]
48
-	public function fetchNumeric(): array|false {
48
+	public function fetchNumeric(): array | false {
49 49
 		return $this->inner->fetchNumeric();
50 50
 	}
51 51
 
Please login to merge, or discard this patch.
lib/private/Preview/BackgroundCleanupJob.php 1 patch
Indentation   +64 added lines, -64 removed lines patch added patch discarded remove patch
@@ -20,42 +20,42 @@  discard block
 block discarded – undo
20 20
  */
21 21
 class BackgroundCleanupJob extends TimedJob {
22 22
 
23
-	public function __construct(
24
-		ITimeFactory $timeFactory,
25
-		private readonly IDBConnection $connection,
26
-		private readonly PreviewService $previewService,
27
-		private readonly bool $isCLI,
28
-	) {
29
-		parent::__construct($timeFactory);
30
-		// Run at most once an hour
31
-		$this->setInterval(60 * 60);
32
-		$this->setTimeSensitivity(self::TIME_INSENSITIVE);
33
-	}
23
+    public function __construct(
24
+        ITimeFactory $timeFactory,
25
+        private readonly IDBConnection $connection,
26
+        private readonly PreviewService $previewService,
27
+        private readonly bool $isCLI,
28
+    ) {
29
+        parent::__construct($timeFactory);
30
+        // Run at most once an hour
31
+        $this->setInterval(60 * 60);
32
+        $this->setTimeSensitivity(self::TIME_INSENSITIVE);
33
+    }
34 34
 
35
-	public function run($argument): void {
36
-		foreach ($this->getDeletedFiles() as $fileId) {
37
-			$previewIds = [];
38
-			foreach ($this->previewService->getAvailablePreviewsForFile($fileId) as $preview) {
39
-				$this->previewService->deletePreview($preview);
40
-			}
41
-		}
42
-	}
35
+    public function run($argument): void {
36
+        foreach ($this->getDeletedFiles() as $fileId) {
37
+            $previewIds = [];
38
+            foreach ($this->previewService->getAvailablePreviewsForFile($fileId) as $preview) {
39
+                $this->previewService->deletePreview($preview);
40
+            }
41
+        }
42
+    }
43 43
 
44
-	/**
45
-	 * @return \Iterator<FileId>
46
-	 */
47
-	private function getDeletedFiles(): \Iterator {
48
-		if ($this->connection->getShardDefinition('filecache')) {
49
-			foreach ($this->previewService->getAvailableFileIds() as $availableFileIdGroup) {
50
-				$fileIds = $this->findMissingSources($availableFileIdGroup['storageId'], $availableFileIdGroup['fileIds']);
51
-				foreach ($fileIds as $fileId) {
52
-					yield $fileId;
53
-				}
54
-			}
55
-			return;
56
-		}
44
+    /**
45
+     * @return \Iterator<FileId>
46
+     */
47
+    private function getDeletedFiles(): \Iterator {
48
+        if ($this->connection->getShardDefinition('filecache')) {
49
+            foreach ($this->previewService->getAvailableFileIds() as $availableFileIdGroup) {
50
+                $fileIds = $this->findMissingSources($availableFileIdGroup['storageId'], $availableFileIdGroup['fileIds']);
51
+                foreach ($fileIds as $fileId) {
52
+                    yield $fileId;
53
+                }
54
+            }
55
+            return;
56
+        }
57 57
 
58
-		/*
58
+        /*
59 59
 		 * Deleting a file will not delete related previews right away.
60 60
 		 *
61 61
 		 * A delete request is usually an HTTP request.
@@ -70,38 +70,38 @@  discard block
 block discarded – undo
70 70
 		 *
71 71
 		 * If the related file is deleted, b.fileid will be null and the preview folder can be deleted.
72 72
 		 */
73
-		$qb = $this->connection->getQueryBuilder();
74
-		$qb->select('p.file_id')
75
-			->from('previews', 'p')
76
-			->leftJoin('p', 'filecache', 'f', $qb->expr()->eq(
77
-				'p.file_id', 'f.fileid'
78
-			))
79
-			->where($qb->expr()->isNull('f.fileid'));
73
+        $qb = $this->connection->getQueryBuilder();
74
+        $qb->select('p.file_id')
75
+            ->from('previews', 'p')
76
+            ->leftJoin('p', 'filecache', 'f', $qb->expr()->eq(
77
+                'p.file_id', 'f.fileid'
78
+            ))
79
+            ->where($qb->expr()->isNull('f.fileid'));
80 80
 
81
-		if (!$this->isCLI) {
82
-			$qb->setMaxResults(10);
83
-		}
81
+        if (!$this->isCLI) {
82
+            $qb->setMaxResults(10);
83
+        }
84 84
 
85
-		$cursor = $qb->executeQuery();
86
-		while ($row = $cursor->fetch()) {
87
-			yield (int)$row['file_id'];
88
-		}
89
-		$cursor->closeCursor();
90
-	}
85
+        $cursor = $qb->executeQuery();
86
+        while ($row = $cursor->fetch()) {
87
+            yield (int)$row['file_id'];
88
+        }
89
+        $cursor->closeCursor();
90
+    }
91 91
 
92
-	/**
93
-	 * @param FileId[] $ids
94
-	 * @return FileId[]
95
-	 */
96
-	private function findMissingSources(int $storage, array $ids): array {
97
-		$qb = $this->connection->getQueryBuilder();
98
-		$qb->select('fileid')
99
-			->from('filecache')
100
-			->where($qb->expr()->andX(
101
-				$qb->expr()->in('fileid', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)),
102
-				$qb->expr()->eq('storage', $qb->createNamedParameter($storage, IQueryBuilder::PARAM_INT)),
103
-			));
104
-		$found = $qb->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
105
-		return array_diff($ids, $found);
106
-	}
92
+    /**
93
+     * @param FileId[] $ids
94
+     * @return FileId[]
95
+     */
96
+    private function findMissingSources(int $storage, array $ids): array {
97
+        $qb = $this->connection->getQueryBuilder();
98
+        $qb->select('fileid')
99
+            ->from('filecache')
100
+            ->where($qb->expr()->andX(
101
+                $qb->expr()->in('fileid', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)),
102
+                $qb->expr()->eq('storage', $qb->createNamedParameter($storage, IQueryBuilder::PARAM_INT)),
103
+            ));
104
+        $found = $qb->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
105
+        return array_diff($ids, $found);
106
+    }
107 107
 }
Please login to merge, or discard this patch.
lib/private/Preview/Storage/ObjectStorePreviewStorage.php 1 patch
Indentation   +157 added lines, -157 removed lines patch added patch discarded remove patch
@@ -26,161 +26,161 @@
 block discarded – undo
26 26
  */
27 27
 class ObjectStorePreviewStorage implements IPreviewStorage {
28 28
 
29
-	/**
30
-	 * @var array<string, array<string, IObjectStore>>
31
-	 */
32
-	private array $objectStoreCache = [];
33
-
34
-	private bool $isMultibucketPreviewDistributionEnabled;
35
-
36
-	public function __construct(
37
-		private readonly PrimaryObjectStoreConfig $objectStoreConfig,
38
-		IConfig $config,
39
-		private readonly PreviewMapper $previewMapper,
40
-	) {
41
-		$this->isMultibucketPreviewDistributionEnabled = $config->getSystemValueBool('objectstore.multibucket.preview-distribution');
42
-	}
43
-
44
-	#[Override]
45
-	public function writePreview(Preview $preview, mixed $stream): int {
46
-		$size = 0;
47
-		$countStream = CountWrapper::wrap($stream, function (int $writtenSize) use (&$size): void {
48
-			$size = $writtenSize;
49
-		});
50
-
51
-		[
52
-			'urn' => $urn,
53
-			'store' => $store,
54
-		] = $this->getObjectStoreInfoForNewPreview($preview);
55
-
56
-		try {
57
-			$store->writeObject($urn, $countStream);
58
-		} catch (\Exception $exception) {
59
-			throw new NotPermittedException('Unable to save preview to object store', previous: $exception);
60
-		}
61
-		return $size;
62
-	}
63
-
64
-	#[Override]
65
-	public function readPreview(Preview $preview): mixed {
66
-		[
67
-			'urn' => $urn,
68
-			'store' => $store,
69
-		] = $this->getObjectStoreInfoForExistingPreview($preview);
70
-
71
-		try {
72
-			return $store->readObject($urn);
73
-		} catch (\Exception $exception) {
74
-			throw new NotPermittedException('Unable to read preview from object store with urn:' . $urn, previous: $exception);
75
-		}
76
-	}
77
-
78
-	#[Override]
79
-	public function deletePreview(Preview $preview): void {
80
-		if (defined('PHPUNIT_RUN') && $preview->getLocationId() === null) {
81
-			// Should only be the case in unit tests when adding dummy previews in the database.
82
-			return;
83
-		}
84
-
85
-		[
86
-			'urn' => $urn,
87
-			'store' => $store,
88
-		] = $this->getObjectStoreInfoForExistingPreview($preview);
89
-
90
-		try {
91
-			$store->deleteObject($urn);
92
-		} catch (\Exception $exception) {
93
-			throw new NotPermittedException('Unable to delete preview from object store', previous: $exception);
94
-		}
95
-	}
96
-
97
-	#[Override]
98
-	public function migratePreview(Preview $preview, SimpleFile $file): void {
99
-		// Just set the Preview::bucket and Preview::objectStore
100
-		$this->getObjectStoreInfoForNewPreview($preview, migration: true);
101
-		$this->previewMapper->update($preview);
102
-	}
103
-
104
-	/**
105
-	 * @return ObjectStoreDefinition
106
-	 */
107
-	private function getObjectStoreInfoForExistingPreview(Preview $preview): array {
108
-		$objectStoreName = $preview->getObjectStoreName();
109
-		$bucketName = $preview->getBucketName();
110
-		assert(!empty($objectStoreName));
111
-		assert(!empty($bucketName));
112
-
113
-		$config = $this->objectStoreConfig->getObjectStoreConfiguration($objectStoreName);
114
-		$config['arguments']['bucket'] = $preview->getBucketName();
115
-		$objectStoreName = $preview->getObjectStoreName();
116
-
117
-		return [
118
-			'urn' => $this->getUrn($preview, $config),
119
-			'store' => $this->getObjectStore($objectStoreName, $config),
120
-		];
121
-	}
122
-
123
-	/**
124
-	 * @return ObjectStoreDefinition
125
-	 */
126
-	private function getObjectStoreInfoForNewPreview(Preview $preview, bool $migration = false): array {
127
-		// When migrating old previews, use the 'root' object store configuration
128
-		$config = $this->objectStoreConfig->getObjectStoreConfiguration($migration ? 'root' : 'preview');
129
-		$objectStoreName = $this->objectStoreConfig->resolveAlias($migration ? 'root' : 'preview');
130
-
131
-		$bucketName = $config['arguments']['bucket'];
132
-		if ($config['arguments']['multibucket']) {
133
-			if ($this->isMultibucketPreviewDistributionEnabled) {
134
-				// Spread the previews on different buckets depending on their corresponding fileId
135
-				$oldLocationArray = str_split(substr(md5((string)$preview->getFileId()), 0, 2));
136
-				$bucketNumber = hexdec('0x' . $oldLocationArray[0]) * 16 + hexdec('0x' . $oldLocationArray[0]);
137
-				$bucketName .= '-preview-' . $bucketNumber;
138
-			} else {
139
-				// Put all previews in the root (0) bucket
140
-				$bucketName .= '0';
141
-			}
142
-		}
143
-		$config['arguments']['bucket'] = $bucketName;
144
-
145
-		// Get the locationId corresponding to the bucketName and objectStoreName, this will create
146
-		// a new one, if no matching location is found in the DB.
147
-		$locationId = $this->previewMapper->getLocationId($bucketName, $objectStoreName);
148
-		$preview->setLocationId($locationId);
149
-		$preview->setObjectStoreName($objectStoreName);
150
-		$preview->setBucketName($bucketName);
151
-
152
-		return [
153
-			'urn' => $this->getUrn($preview, $config),
154
-			'store' => $this->getObjectStore($objectStoreName, $config),
155
-		];
156
-	}
157
-
158
-	private function getObjectStore(string $objectStoreName, array $config): IObjectStore {
159
-		$bucketName = $config['arguments']['bucket'];
160
-
161
-		if (!isset($this->objectStoreCache[$objectStoreName])) {
162
-			$this->objectStoreCache[$objectStoreName] = [];
163
-			$this->objectStoreCache[$objectStoreName][$bucketName] = $this->objectStoreConfig->buildObjectStore($config);
164
-		} elseif (!isset($this->objectStoreCache[$objectStoreName][$bucketName])) {
165
-			$this->objectStoreCache[$objectStoreName][$bucketName] = $this->objectStoreConfig->buildObjectStore($config);
166
-		}
167
-
168
-		return $this->objectStoreCache[$objectStoreName][$bucketName];
169
-	}
170
-
171
-	public function getUrn(Preview $preview, array $config): string {
172
-		if ($preview->getOldFileId()) {
173
-			return ($config['arguments']['objectPrefix'] ?? 'urn:oid:') . $preview->getOldFileId();
174
-		}
175
-		if (isset($config['arguments']['objectPrefix'])) {
176
-			return ($config['arguments']['objectPrefix'] . 'preview:') . $preview->getId();
177
-		} else {
178
-			return 'uri:oid:preview:' . $preview->getId();
179
-		}
180
-	}
181
-
182
-	#[Override]
183
-	public function scan(): int {
184
-		return 0;
185
-	}
29
+    /**
30
+     * @var array<string, array<string, IObjectStore>>
31
+     */
32
+    private array $objectStoreCache = [];
33
+
34
+    private bool $isMultibucketPreviewDistributionEnabled;
35
+
36
+    public function __construct(
37
+        private readonly PrimaryObjectStoreConfig $objectStoreConfig,
38
+        IConfig $config,
39
+        private readonly PreviewMapper $previewMapper,
40
+    ) {
41
+        $this->isMultibucketPreviewDistributionEnabled = $config->getSystemValueBool('objectstore.multibucket.preview-distribution');
42
+    }
43
+
44
+    #[Override]
45
+    public function writePreview(Preview $preview, mixed $stream): int {
46
+        $size = 0;
47
+        $countStream = CountWrapper::wrap($stream, function (int $writtenSize) use (&$size): void {
48
+            $size = $writtenSize;
49
+        });
50
+
51
+        [
52
+            'urn' => $urn,
53
+            'store' => $store,
54
+        ] = $this->getObjectStoreInfoForNewPreview($preview);
55
+
56
+        try {
57
+            $store->writeObject($urn, $countStream);
58
+        } catch (\Exception $exception) {
59
+            throw new NotPermittedException('Unable to save preview to object store', previous: $exception);
60
+        }
61
+        return $size;
62
+    }
63
+
64
+    #[Override]
65
+    public function readPreview(Preview $preview): mixed {
66
+        [
67
+            'urn' => $urn,
68
+            'store' => $store,
69
+        ] = $this->getObjectStoreInfoForExistingPreview($preview);
70
+
71
+        try {
72
+            return $store->readObject($urn);
73
+        } catch (\Exception $exception) {
74
+            throw new NotPermittedException('Unable to read preview from object store with urn:' . $urn, previous: $exception);
75
+        }
76
+    }
77
+
78
+    #[Override]
79
+    public function deletePreview(Preview $preview): void {
80
+        if (defined('PHPUNIT_RUN') && $preview->getLocationId() === null) {
81
+            // Should only be the case in unit tests when adding dummy previews in the database.
82
+            return;
83
+        }
84
+
85
+        [
86
+            'urn' => $urn,
87
+            'store' => $store,
88
+        ] = $this->getObjectStoreInfoForExistingPreview($preview);
89
+
90
+        try {
91
+            $store->deleteObject($urn);
92
+        } catch (\Exception $exception) {
93
+            throw new NotPermittedException('Unable to delete preview from object store', previous: $exception);
94
+        }
95
+    }
96
+
97
+    #[Override]
98
+    public function migratePreview(Preview $preview, SimpleFile $file): void {
99
+        // Just set the Preview::bucket and Preview::objectStore
100
+        $this->getObjectStoreInfoForNewPreview($preview, migration: true);
101
+        $this->previewMapper->update($preview);
102
+    }
103
+
104
+    /**
105
+     * @return ObjectStoreDefinition
106
+     */
107
+    private function getObjectStoreInfoForExistingPreview(Preview $preview): array {
108
+        $objectStoreName = $preview->getObjectStoreName();
109
+        $bucketName = $preview->getBucketName();
110
+        assert(!empty($objectStoreName));
111
+        assert(!empty($bucketName));
112
+
113
+        $config = $this->objectStoreConfig->getObjectStoreConfiguration($objectStoreName);
114
+        $config['arguments']['bucket'] = $preview->getBucketName();
115
+        $objectStoreName = $preview->getObjectStoreName();
116
+
117
+        return [
118
+            'urn' => $this->getUrn($preview, $config),
119
+            'store' => $this->getObjectStore($objectStoreName, $config),
120
+        ];
121
+    }
122
+
123
+    /**
124
+     * @return ObjectStoreDefinition
125
+     */
126
+    private function getObjectStoreInfoForNewPreview(Preview $preview, bool $migration = false): array {
127
+        // When migrating old previews, use the 'root' object store configuration
128
+        $config = $this->objectStoreConfig->getObjectStoreConfiguration($migration ? 'root' : 'preview');
129
+        $objectStoreName = $this->objectStoreConfig->resolveAlias($migration ? 'root' : 'preview');
130
+
131
+        $bucketName = $config['arguments']['bucket'];
132
+        if ($config['arguments']['multibucket']) {
133
+            if ($this->isMultibucketPreviewDistributionEnabled) {
134
+                // Spread the previews on different buckets depending on their corresponding fileId
135
+                $oldLocationArray = str_split(substr(md5((string)$preview->getFileId()), 0, 2));
136
+                $bucketNumber = hexdec('0x' . $oldLocationArray[0]) * 16 + hexdec('0x' . $oldLocationArray[0]);
137
+                $bucketName .= '-preview-' . $bucketNumber;
138
+            } else {
139
+                // Put all previews in the root (0) bucket
140
+                $bucketName .= '0';
141
+            }
142
+        }
143
+        $config['arguments']['bucket'] = $bucketName;
144
+
145
+        // Get the locationId corresponding to the bucketName and objectStoreName, this will create
146
+        // a new one, if no matching location is found in the DB.
147
+        $locationId = $this->previewMapper->getLocationId($bucketName, $objectStoreName);
148
+        $preview->setLocationId($locationId);
149
+        $preview->setObjectStoreName($objectStoreName);
150
+        $preview->setBucketName($bucketName);
151
+
152
+        return [
153
+            'urn' => $this->getUrn($preview, $config),
154
+            'store' => $this->getObjectStore($objectStoreName, $config),
155
+        ];
156
+    }
157
+
158
+    private function getObjectStore(string $objectStoreName, array $config): IObjectStore {
159
+        $bucketName = $config['arguments']['bucket'];
160
+
161
+        if (!isset($this->objectStoreCache[$objectStoreName])) {
162
+            $this->objectStoreCache[$objectStoreName] = [];
163
+            $this->objectStoreCache[$objectStoreName][$bucketName] = $this->objectStoreConfig->buildObjectStore($config);
164
+        } elseif (!isset($this->objectStoreCache[$objectStoreName][$bucketName])) {
165
+            $this->objectStoreCache[$objectStoreName][$bucketName] = $this->objectStoreConfig->buildObjectStore($config);
166
+        }
167
+
168
+        return $this->objectStoreCache[$objectStoreName][$bucketName];
169
+    }
170
+
171
+    public function getUrn(Preview $preview, array $config): string {
172
+        if ($preview->getOldFileId()) {
173
+            return ($config['arguments']['objectPrefix'] ?? 'urn:oid:') . $preview->getOldFileId();
174
+        }
175
+        if (isset($config['arguments']['objectPrefix'])) {
176
+            return ($config['arguments']['objectPrefix'] . 'preview:') . $preview->getId();
177
+        } else {
178
+            return 'uri:oid:preview:' . $preview->getId();
179
+        }
180
+    }
181
+
182
+    #[Override]
183
+    public function scan(): int {
184
+        return 0;
185
+    }
186 186
 }
Please login to merge, or discard this patch.
lib/private/AppConfig.php 2 patches
Indentation   +1794 added lines, -1794 removed lines patch added patch discarded remove patch
@@ -51,1798 +51,1798 @@
 block discarded – undo
51 51
  * @since 29.0.0 - Supporting types and lazy loading
52 52
  */
53 53
 class AppConfig implements IAppConfig {
54
-	private const APP_MAX_LENGTH = 32;
55
-	private const KEY_MAX_LENGTH = 64;
56
-	private const ENCRYPTION_PREFIX = '$AppConfigEncryption$';
57
-	private const ENCRYPTION_PREFIX_LENGTH = 21; // strlen(self::ENCRYPTION_PREFIX)
58
-	private const LOCAL_CACHE_KEY = 'OC\\AppConfig';
59
-	private const LOCAL_CACHE_TTL = 3;
60
-
61
-	/** @var array<string, array<string, string>> ['app_id' => ['config_key' => 'config_value']] */
62
-	private array $fastCache = [];   // cache for normal config keys
63
-	/** @var array<string, array<string, string>> ['app_id' => ['config_key' => 'config_value']] */
64
-	private array $lazyCache = [];   // cache for lazy config keys
65
-	/** @var array<string, array<string, int>> ['app_id' => ['config_key' => bitflag]] */
66
-	private array $valueTypes = [];  // type for all config values
67
-	private bool $fastLoaded = false;
68
-	private bool $lazyLoaded = false;
69
-	/** @var array<string, array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
70
-	private array $configLexiconDetails = [];
71
-	private bool $ignoreLexiconAliases = false;
72
-	private array $strictnessApplied = [];
73
-
74
-	/** @var ?array<string, string> */
75
-	private ?array $appVersionsCache = null;
76
-	private ?ICache $localCache = null;
77
-
78
-	public function __construct(
79
-		protected IDBConnection $connection,
80
-		protected IConfig $config,
81
-		private readonly ConfigManager $configManager,
82
-		private readonly PresetManager $presetManager,
83
-		protected LoggerInterface $logger,
84
-		protected ICrypto $crypto,
85
-		public readonly CacheFactory $cacheFactory,
86
-	) {
87
-		if ($config->getSystemValueBool('cache_app_config', true) && $cacheFactory->isLocalCacheAvailable()) {
88
-			$cacheFactory->withServerVersionPrefix(function (ICacheFactory $factory) {
89
-				$this->localCache = $factory->createLocal();
90
-			});
91
-		}
92
-	}
93
-
94
-	/**
95
-	 * @inheritDoc
96
-	 *
97
-	 * @return list<string> list of app ids
98
-	 * @since 7.0.0
99
-	 */
100
-	public function getApps(): array {
101
-		$this->loadConfig(lazy: true);
102
-		$apps = array_merge(array_keys($this->fastCache), array_keys($this->lazyCache));
103
-		sort($apps);
104
-
105
-		return array_values(array_unique($apps));
106
-	}
107
-
108
-	/**
109
-	 * @inheritDoc
110
-	 *
111
-	 * @param string $app id of the app
112
-	 * @return list<string> list of stored config keys
113
-	 * @see searchKeys to not load lazy config keys
114
-	 *
115
-	 * @since 29.0.0
116
-	 */
117
-	public function getKeys(string $app): array {
118
-		$this->assertParams($app);
119
-		$this->loadConfig($app, true);
120
-		$keys = array_merge(array_keys($this->fastCache[$app] ?? []), array_keys($this->lazyCache[$app] ?? []));
121
-		sort($keys);
122
-
123
-		return array_values(array_unique($keys));
124
-	}
125
-
126
-	/**
127
-	 * @inheritDoc
128
-	 *
129
-	 * @param string $app id of the app
130
-	 * @param string $prefix returns only keys starting with this value
131
-	 * @param bool $lazy TRUE to search in lazy config keys
132
-	 * @return list<string> list of stored config keys
133
-	 * @since 32.0.0
134
-	 */
135
-	public function searchKeys(string $app, string $prefix = '', bool $lazy = false): array {
136
-		$this->assertParams($app);
137
-		$this->loadConfig($app, $lazy);
138
-		if ($lazy) {
139
-			$keys = array_keys($this->lazyCache[$app] ?? []);
140
-		} else {
141
-			$keys = array_keys($this->fastCache[$app] ?? []);
142
-		}
143
-
144
-		if ($prefix !== '') {
145
-			$keys = array_filter($keys, static fn (string $key): bool => str_starts_with($key, $prefix));
146
-		}
147
-
148
-		sort($keys);
149
-		return array_values(array_unique($keys));
150
-	}
151
-
152
-	/**
153
-	 * @inheritDoc
154
-	 *
155
-	 * @param string $app id of the app
156
-	 * @param string $key config key
157
-	 * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
158
-	 *
159
-	 * @return bool TRUE if key exists
160
-	 * @since 7.0.0
161
-	 * @since 29.0.0 Added the $lazy argument
162
-	 */
163
-	public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
164
-		$this->assertParams($app, $key);
165
-		$this->loadConfig($app, $lazy ?? true);
166
-		$this->matchAndApplyLexiconDefinition($app, $key);
167
-
168
-		$hasLazy = isset($this->lazyCache[$app][$key]);
169
-		$hasFast = isset($this->fastCache[$app][$key]);
170
-		if ($lazy === null) {
171
-			return $hasLazy || $hasFast;
172
-		} else {
173
-			return $lazy ? $hasLazy : $hasFast;
174
-		}
175
-	}
176
-
177
-	/**
178
-	 * @param string $app id of the app
179
-	 * @param string $key config key
180
-	 * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
181
-	 *
182
-	 * @return bool
183
-	 * @throws AppConfigUnknownKeyException if config key is not known
184
-	 * @since 29.0.0
185
-	 */
186
-	public function isSensitive(string $app, string $key, ?bool $lazy = false): bool {
187
-		$this->assertParams($app, $key);
188
-		$this->loadConfig(null, $lazy ?? true);
189
-		$this->matchAndApplyLexiconDefinition($app, $key);
190
-
191
-		if (!isset($this->valueTypes[$app][$key])) {
192
-			throw new AppConfigUnknownKeyException('unknown config key');
193
-		}
194
-
195
-		return $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key]);
196
-	}
197
-
198
-	/**
199
-	 * @inheritDoc
200
-	 *
201
-	 * @param string $app if of the app
202
-	 * @param string $key config key
203
-	 *
204
-	 * @return bool TRUE if config is lazy loaded
205
-	 * @throws AppConfigUnknownKeyException if config key is not known
206
-	 * @see IAppConfig for details about lazy loading
207
-	 * @since 29.0.0
208
-	 */
209
-	public function isLazy(string $app, string $key): bool {
210
-		$this->assertParams($app, $key);
211
-		$this->matchAndApplyLexiconDefinition($app, $key);
212
-
213
-		// there is a huge probability the non-lazy config are already loaded
214
-		if ($this->hasKey($app, $key, false)) {
215
-			return false;
216
-		}
217
-
218
-		// key not found, we search in the lazy config
219
-		if ($this->hasKey($app, $key, true)) {
220
-			return true;
221
-		}
222
-
223
-		throw new AppConfigUnknownKeyException('unknown config key');
224
-	}
225
-
226
-
227
-	/**
228
-	 * @inheritDoc
229
-	 *
230
-	 * @param string $app id of the app
231
-	 * @param string $prefix config keys prefix to search
232
-	 * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
233
-	 *
234
-	 * @return array<string, string|int|float|bool|array> [configKey => configValue]
235
-	 * @since 29.0.0
236
-	 */
237
-	public function getAllValues(string $app, string $prefix = '', bool $filtered = false): array {
238
-		$this->assertParams($app, $prefix);
239
-		// if we want to filter values, we need to get sensitivity
240
-		$this->loadConfig($app, true);
241
-		// array_merge() will remove numeric keys (here config keys), so addition arrays instead
242
-		$values = $this->formatAppValues($app, ($this->fastCache[$app] ?? []) + ($this->lazyCache[$app] ?? []));
243
-		$values = array_filter(
244
-			$values,
245
-			function (string $key) use ($prefix): bool {
246
-				return str_starts_with($key, $prefix); // filter values based on $prefix
247
-			}, ARRAY_FILTER_USE_KEY
248
-		);
249
-
250
-		if (!$filtered) {
251
-			return $values;
252
-		}
253
-
254
-		/**
255
-		 * Using the old (deprecated) list of sensitive values.
256
-		 */
257
-		foreach ($this->getSensitiveKeys($app) as $sensitiveKeyExp) {
258
-			$sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
259
-			foreach ($sensitiveKeys as $sensitiveKey) {
260
-				$this->valueTypes[$app][$sensitiveKey] = ($this->valueTypes[$app][$sensitiveKey] ?? 0) | self::VALUE_SENSITIVE;
261
-			}
262
-		}
263
-
264
-		$result = [];
265
-		foreach ($values as $key => $value) {
266
-			$result[$key] = $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key] ?? 0) ? IConfig::SENSITIVE_VALUE : $value;
267
-		}
268
-
269
-		return $result;
270
-	}
271
-
272
-	/**
273
-	 * @inheritDoc
274
-	 *
275
-	 * @param string $key config key
276
-	 * @param bool $lazy search within lazy loaded config
277
-	 * @param int|null $typedAs enforce type for the returned values ({@see self::VALUE_STRING} and others)
278
-	 *
279
-	 * @return array<string, string|int|float|bool|array> [appId => configValue]
280
-	 * @since 29.0.0
281
-	 */
282
-	public function searchValues(string $key, bool $lazy = false, ?int $typedAs = null): array {
283
-		$this->assertParams('', $key, true);
284
-		$this->loadConfig(null, $lazy);
285
-
286
-		/** @var array<array-key, array<array-key, mixed>> $cache */
287
-		if ($lazy) {
288
-			$cache = $this->lazyCache;
289
-		} else {
290
-			$cache = $this->fastCache;
291
-		}
292
-
293
-		$values = [];
294
-		foreach (array_keys($cache) as $app) {
295
-			if (isset($cache[$app][$key])) {
296
-				$values[$app] = $this->convertTypedValue($cache[$app][$key], $typedAs ?? $this->getValueType((string)$app, $key, $lazy));
297
-			}
298
-		}
299
-
300
-		return $values;
301
-	}
302
-
303
-
304
-	/**
305
-	 * Get the config value as string.
306
-	 * If the value does not exist the given default will be returned.
307
-	 *
308
-	 * Set lazy to `null` to ignore it and get the value from either source.
309
-	 *
310
-	 * **WARNING:** Method is internal and **SHOULD** not be used, as it is better to get the value with a type.
311
-	 *
312
-	 * @param string $app id of the app
313
-	 * @param string $key config key
314
-	 * @param string $default config value
315
-	 * @param null|bool $lazy get config as lazy loaded or not. can be NULL
316
-	 *
317
-	 * @return string the value or $default
318
-	 * @internal
319
-	 * @since 29.0.0
320
-	 * @see IAppConfig for explanation about lazy loading
321
-	 * @see getValueString()
322
-	 * @see getValueInt()
323
-	 * @see getValueFloat()
324
-	 * @see getValueBool()
325
-	 * @see getValueArray()
326
-	 */
327
-	public function getValueMixed(
328
-		string $app,
329
-		string $key,
330
-		string $default = '',
331
-		?bool $lazy = false,
332
-	): string {
333
-		try {
334
-			$lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy;
335
-		} catch (AppConfigUnknownKeyException) {
336
-			return $default;
337
-		}
338
-
339
-		return $this->getTypedValue(
340
-			$app,
341
-			$key,
342
-			$default,
343
-			$lazy,
344
-			self::VALUE_MIXED
345
-		);
346
-	}
347
-
348
-	/**
349
-	 * @inheritDoc
350
-	 *
351
-	 * @param string $app id of the app
352
-	 * @param string $key config key
353
-	 * @param string $default default value
354
-	 * @param bool $lazy search within lazy loaded config
355
-	 *
356
-	 * @return string stored config value or $default if not set in database
357
-	 * @throws InvalidArgumentException if one of the argument format is invalid
358
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
359
-	 * @since 29.0.0
360
-	 * @see IAppConfig for explanation about lazy loading
361
-	 */
362
-	public function getValueString(
363
-		string $app,
364
-		string $key,
365
-		string $default = '',
366
-		bool $lazy = false,
367
-	): string {
368
-		return $this->getTypedValue($app, $key, $default, $lazy, self::VALUE_STRING);
369
-	}
370
-
371
-	/**
372
-	 * @inheritDoc
373
-	 *
374
-	 * @param string $app id of the app
375
-	 * @param string $key config key
376
-	 * @param int $default default value
377
-	 * @param bool $lazy search within lazy loaded config
378
-	 *
379
-	 * @return int stored config value or $default if not set in database
380
-	 * @throws InvalidArgumentException if one of the argument format is invalid
381
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
382
-	 * @since 29.0.0
383
-	 * @see IAppConfig for explanation about lazy loading
384
-	 */
385
-	public function getValueInt(
386
-		string $app,
387
-		string $key,
388
-		int $default = 0,
389
-		bool $lazy = false,
390
-	): int {
391
-		return (int)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_INT);
392
-	}
393
-
394
-	/**
395
-	 * @inheritDoc
396
-	 *
397
-	 * @param string $app id of the app
398
-	 * @param string $key config key
399
-	 * @param float $default default value
400
-	 * @param bool $lazy search within lazy loaded config
401
-	 *
402
-	 * @return float stored config value or $default if not set in database
403
-	 * @throws InvalidArgumentException if one of the argument format is invalid
404
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
405
-	 * @since 29.0.0
406
-	 * @see IAppConfig for explanation about lazy loading
407
-	 */
408
-	public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float {
409
-		return (float)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_FLOAT);
410
-	}
411
-
412
-	/**
413
-	 * @inheritDoc
414
-	 *
415
-	 * @param string $app id of the app
416
-	 * @param string $key config key
417
-	 * @param bool $default default value
418
-	 * @param bool $lazy search within lazy loaded config
419
-	 *
420
-	 * @return bool stored config value or $default if not set in database
421
-	 * @throws InvalidArgumentException if one of the argument format is invalid
422
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
423
-	 * @since 29.0.0
424
-	 * @see IAppConfig for explanation about lazy loading
425
-	 */
426
-	public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool {
427
-		$b = strtolower($this->getTypedValue($app, $key, $default ? 'true' : 'false', $lazy, self::VALUE_BOOL));
428
-		return in_array($b, ['1', 'true', 'yes', 'on']);
429
-	}
430
-
431
-	/**
432
-	 * @inheritDoc
433
-	 *
434
-	 * @param string $app id of the app
435
-	 * @param string $key config key
436
-	 * @param array $default default value
437
-	 * @param bool $lazy search within lazy loaded config
438
-	 *
439
-	 * @return array stored config value or $default if not set in database
440
-	 * @throws InvalidArgumentException if one of the argument format is invalid
441
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
442
-	 * @since 29.0.0
443
-	 * @see IAppConfig for explanation about lazy loading
444
-	 */
445
-	public function getValueArray(
446
-		string $app,
447
-		string $key,
448
-		array $default = [],
449
-		bool $lazy = false,
450
-	): array {
451
-		try {
452
-			$defaultJson = json_encode($default, JSON_THROW_ON_ERROR);
453
-			$value = json_decode($this->getTypedValue($app, $key, $defaultJson, $lazy, self::VALUE_ARRAY), true, flags: JSON_THROW_ON_ERROR);
454
-
455
-			return is_array($value) ? $value : [];
456
-		} catch (JsonException) {
457
-			return [];
458
-		}
459
-	}
460
-
461
-	/**
462
-	 * @param string $app id of the app
463
-	 * @param string $key config key
464
-	 * @param string $default default value
465
-	 * @param bool $lazy search within lazy loaded config
466
-	 * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT}{@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
467
-	 *
468
-	 * @return string
469
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
470
-	 * @throws InvalidArgumentException
471
-	 */
472
-	private function getTypedValue(
473
-		string $app,
474
-		string $key,
475
-		string $default,
476
-		bool $lazy,
477
-		int $type,
478
-	): string {
479
-		$this->assertParams($app, $key, valueType: $type);
480
-		$origKey = $key;
481
-		$matched = $this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default);
482
-		if ($default === null) {
483
-			// there is no logical reason for it to be null
484
-			throw new \Exception('default cannot be null');
485
-		}
486
-
487
-		// returns default if strictness of lexicon is set to WARNING (block and report)
488
-		if (!$matched) {
489
-			return $default;
490
-		}
491
-
492
-		$this->loadConfig($app, $lazy ?? true);
493
-
494
-		/**
495
-		 * We ignore check if mixed type is requested.
496
-		 * If type of stored value is set as mixed, we don't filter.
497
-		 * If type of stored value is defined, we compare with the one requested.
498
-		 */
499
-		$knownType = $this->valueTypes[$app][$key] ?? 0;
500
-		if (!$this->isTyped(self::VALUE_MIXED, $type)
501
-			&& $knownType > 0
502
-			&& !$this->isTyped(self::VALUE_MIXED, $knownType)
503
-			&& !$this->isTyped($type, $knownType)) {
504
-			$this->logger->warning('conflict with value type from database', ['app' => $app, 'key' => $key, 'type' => $type, 'knownType' => $knownType]);
505
-			throw new AppConfigTypeConflictException('conflict with value type from database');
506
-		}
507
-
508
-		/**
509
-		 * - the pair $app/$key cannot exist in both array,
510
-		 * - we should still return an existing non-lazy value even if current method
511
-		 *   is called with $lazy is true
512
-		 *
513
-		 * This way, lazyCache will be empty until the load for lazy config value is requested.
514
-		 */
515
-		if (isset($this->lazyCache[$app][$key])) {
516
-			$value = $this->lazyCache[$app][$key];
517
-		} elseif (isset($this->fastCache[$app][$key])) {
518
-			$value = $this->fastCache[$app][$key];
519
-		} else {
520
-			return $default;
521
-		}
522
-
523
-		$sensitive = $this->isTyped(self::VALUE_SENSITIVE, $knownType);
524
-		if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
525
-			// Only decrypt values that are stored encrypted
526
-			$value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
527
-		}
528
-
529
-		// in case the key was modified while running matchAndApplyLexiconDefinition() we are
530
-		// interested to check options in case a modification of the value is needed
531
-		// ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN
532
-		if ($origKey !== $key && $type === self::VALUE_BOOL) {
533
-			$value = ($this->configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0';
534
-		}
535
-
536
-		return $value;
537
-	}
538
-
539
-	/**
540
-	 * @inheritDoc
541
-	 *
542
-	 * @param string $app id of the app
543
-	 * @param string $key config key
544
-	 *
545
-	 * @return int type of the value
546
-	 * @throws AppConfigUnknownKeyException if config key is not known
547
-	 * @since 29.0.0
548
-	 * @see VALUE_STRING
549
-	 * @see VALUE_INT
550
-	 * @see VALUE_FLOAT
551
-	 * @see VALUE_BOOL
552
-	 * @see VALUE_ARRAY
553
-	 */
554
-	public function getValueType(string $app, string $key, ?bool $lazy = null): int {
555
-		$type = self::VALUE_MIXED;
556
-		$ignorable = $lazy ?? false;
557
-		$this->matchAndApplyLexiconDefinition($app, $key, $ignorable, $type);
558
-		if ($type !== self::VALUE_MIXED) {
559
-			// a modified $type means config key is set in Lexicon
560
-			return $type;
561
-		}
562
-
563
-		$this->assertParams($app, $key);
564
-		$this->loadConfig($app, $lazy ?? true);
565
-
566
-		if (!isset($this->valueTypes[$app][$key])) {
567
-			throw new AppConfigUnknownKeyException('unknown config key');
568
-		}
569
-
570
-		$type = $this->valueTypes[$app][$key];
571
-		$type &= ~self::VALUE_SENSITIVE;
572
-		return $type;
573
-	}
574
-
575
-
576
-	/**
577
-	 * Store a config key and its value in database as VALUE_MIXED
578
-	 *
579
-	 * **WARNING:** Method is internal and **MUST** not be used as it is best to set a real value type
580
-	 *
581
-	 * @param string $app id of the app
582
-	 * @param string $key config key
583
-	 * @param string $value config value
584
-	 * @param bool $lazy set config as lazy loaded
585
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
586
-	 *
587
-	 * @return bool TRUE if value was different, therefor updated in database
588
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED
589
-	 * @internal
590
-	 * @since 29.0.0
591
-	 * @see IAppConfig for explanation about lazy loading
592
-	 * @see setValueString()
593
-	 * @see setValueInt()
594
-	 * @see setValueFloat()
595
-	 * @see setValueBool()
596
-	 * @see setValueArray()
597
-	 */
598
-	public function setValueMixed(
599
-		string $app,
600
-		string $key,
601
-		string $value,
602
-		bool $lazy = false,
603
-		bool $sensitive = false,
604
-	): bool {
605
-		return $this->setTypedValue(
606
-			$app,
607
-			$key,
608
-			$value,
609
-			$lazy,
610
-			self::VALUE_MIXED | ($sensitive ? self::VALUE_SENSITIVE : 0)
611
-		);
612
-	}
613
-
614
-
615
-	/**
616
-	 * @inheritDoc
617
-	 *
618
-	 * @param string $app id of the app
619
-	 * @param string $key config key
620
-	 * @param string $value config value
621
-	 * @param bool $lazy set config as lazy loaded
622
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
623
-	 *
624
-	 * @return bool TRUE if value was different, therefor updated in database
625
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
626
-	 * @since 29.0.0
627
-	 * @see IAppConfig for explanation about lazy loading
628
-	 */
629
-	public function setValueString(
630
-		string $app,
631
-		string $key,
632
-		string $value,
633
-		bool $lazy = false,
634
-		bool $sensitive = false,
635
-	): bool {
636
-		return $this->setTypedValue(
637
-			$app,
638
-			$key,
639
-			$value,
640
-			$lazy,
641
-			self::VALUE_STRING | ($sensitive ? self::VALUE_SENSITIVE : 0)
642
-		);
643
-	}
644
-
645
-	/**
646
-	 * @inheritDoc
647
-	 *
648
-	 * @param string $app id of the app
649
-	 * @param string $key config key
650
-	 * @param int $value config value
651
-	 * @param bool $lazy set config as lazy loaded
652
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
653
-	 *
654
-	 * @return bool TRUE if value was different, therefor updated in database
655
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
656
-	 * @since 29.0.0
657
-	 * @see IAppConfig for explanation about lazy loading
658
-	 */
659
-	public function setValueInt(
660
-		string $app,
661
-		string $key,
662
-		int $value,
663
-		bool $lazy = false,
664
-		bool $sensitive = false,
665
-	): bool {
666
-		if ($value > 2000000000) {
667
-			$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.');
668
-		}
669
-
670
-		return $this->setTypedValue(
671
-			$app,
672
-			$key,
673
-			(string)$value,
674
-			$lazy,
675
-			self::VALUE_INT | ($sensitive ? self::VALUE_SENSITIVE : 0)
676
-		);
677
-	}
678
-
679
-	/**
680
-	 * @inheritDoc
681
-	 *
682
-	 * @param string $app id of the app
683
-	 * @param string $key config key
684
-	 * @param float $value config value
685
-	 * @param bool $lazy set config as lazy loaded
686
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
687
-	 *
688
-	 * @return bool TRUE if value was different, therefor updated in database
689
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
690
-	 * @since 29.0.0
691
-	 * @see IAppConfig for explanation about lazy loading
692
-	 */
693
-	public function setValueFloat(
694
-		string $app,
695
-		string $key,
696
-		float $value,
697
-		bool $lazy = false,
698
-		bool $sensitive = false,
699
-	): bool {
700
-		return $this->setTypedValue(
701
-			$app,
702
-			$key,
703
-			(string)$value,
704
-			$lazy,
705
-			self::VALUE_FLOAT | ($sensitive ? self::VALUE_SENSITIVE : 0)
706
-		);
707
-	}
708
-
709
-	/**
710
-	 * @inheritDoc
711
-	 *
712
-	 * @param string $app id of the app
713
-	 * @param string $key config key
714
-	 * @param bool $value config value
715
-	 * @param bool $lazy set config as lazy loaded
716
-	 *
717
-	 * @return bool TRUE if value was different, therefor updated in database
718
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
719
-	 * @since 29.0.0
720
-	 * @see IAppConfig for explanation about lazy loading
721
-	 */
722
-	public function setValueBool(
723
-		string $app,
724
-		string $key,
725
-		bool $value,
726
-		bool $lazy = false,
727
-	): bool {
728
-		return $this->setTypedValue(
729
-			$app,
730
-			$key,
731
-			($value) ? '1' : '0',
732
-			$lazy,
733
-			self::VALUE_BOOL
734
-		);
735
-	}
736
-
737
-	/**
738
-	 * @inheritDoc
739
-	 *
740
-	 * @param string $app id of the app
741
-	 * @param string $key config key
742
-	 * @param array $value config value
743
-	 * @param bool $lazy set config as lazy loaded
744
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
745
-	 *
746
-	 * @return bool TRUE if value was different, therefor updated in database
747
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
748
-	 * @throws JsonException
749
-	 * @since 29.0.0
750
-	 * @see IAppConfig for explanation about lazy loading
751
-	 */
752
-	public function setValueArray(
753
-		string $app,
754
-		string $key,
755
-		array $value,
756
-		bool $lazy = false,
757
-		bool $sensitive = false,
758
-	): bool {
759
-		try {
760
-			return $this->setTypedValue(
761
-				$app,
762
-				$key,
763
-				json_encode($value, JSON_THROW_ON_ERROR),
764
-				$lazy,
765
-				self::VALUE_ARRAY | ($sensitive ? self::VALUE_SENSITIVE : 0)
766
-			);
767
-		} catch (JsonException $e) {
768
-			$this->logger->warning('could not setValueArray', ['app' => $app, 'key' => $key, 'exception' => $e]);
769
-			throw $e;
770
-		}
771
-	}
772
-
773
-	/**
774
-	 * Store a config key and its value in database
775
-	 *
776
-	 * If config key is already known with the exact same config value and same sensitive/lazy status, the
777
-	 * database is not updated. If config value was previously stored as sensitive, status will not be
778
-	 * altered.
779
-	 *
780
-	 * @param string $app id of the app
781
-	 * @param string $key config key
782
-	 * @param string $value config value
783
-	 * @param bool $lazy config set as lazy loaded
784
-	 * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
785
-	 *
786
-	 * @return bool TRUE if value was updated in database
787
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
788
-	 * @see IAppConfig for explanation about lazy loading
789
-	 */
790
-	private function setTypedValue(
791
-		string $app,
792
-		string $key,
793
-		string $value,
794
-		bool $lazy,
795
-		int $type,
796
-	): bool {
797
-		$this->assertParams($app, $key);
798
-		if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type)) {
799
-			return false; // returns false as database is not updated
800
-		}
801
-		$this->loadConfig(null, $lazy ?? true);
802
-
803
-		$sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
804
-		$inserted = $refreshCache = false;
805
-
806
-		$origValue = $value;
807
-		if ($sensitive || ($this->hasKey($app, $key, $lazy) && $this->isSensitive($app, $key, $lazy))) {
808
-			$value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
809
-		}
810
-
811
-		if ($this->hasKey($app, $key, $lazy)) {
812
-			/**
813
-			 * no update if key is already known with set lazy status and value is
814
-			 * not different, unless sensitivity is switched from false to true.
815
-			 */
816
-			if ($origValue === $this->getTypedValue($app, $key, $value, $lazy ?? true, $type)
817
-				&& (!$sensitive || $this->isSensitive($app, $key, $lazy))) {
818
-				return false;
819
-			}
820
-		} else {
821
-			/**
822
-			 * if key is not known yet, we try to insert.
823
-			 * It might fail if the key exists with a different lazy flag.
824
-			 */
825
-			try {
826
-				$insert = $this->connection->getQueryBuilder();
827
-				$insert->insert('appconfig')
828
-					->setValue('appid', $insert->createNamedParameter($app))
829
-					->setValue('lazy', $insert->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
830
-					->setValue('type', $insert->createNamedParameter($type, IQueryBuilder::PARAM_INT))
831
-					->setValue('configkey', $insert->createNamedParameter($key))
832
-					->setValue('configvalue', $insert->createNamedParameter($value));
833
-				$insert->executeStatement();
834
-				$inserted = true;
835
-			} catch (DBException $e) {
836
-				if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
837
-					throw $e; // TODO: throw exception or just log and returns false !?
838
-				}
839
-			}
840
-		}
841
-
842
-		/**
843
-		 * We cannot insert a new row, meaning we need to update an already existing one
844
-		 */
845
-		if (!$inserted) {
846
-			$currType = $this->valueTypes[$app][$key] ?? 0;
847
-			if ($currType === 0) { // this might happen when switching lazy loading status
848
-				$this->loadConfig(lazy: true);
849
-				$currType = $this->valueTypes[$app][$key] ?? 0;
850
-			}
851
-
852
-			/**
853
-			 * This should only happen during the upgrade process from 28 to 29.
854
-			 * We only log a warning and set it to VALUE_MIXED.
855
-			 */
856
-			if ($currType === 0) {
857
-				$this->logger->warning('Value type is set to zero (0) in database. This is fine only during the upgrade process from 28 to 29.', ['app' => $app, 'key' => $key]);
858
-				$currType = self::VALUE_MIXED;
859
-			}
860
-
861
-			/**
862
-			 * we only accept a different type from the one stored in database
863
-			 * if the one stored in database is not-defined (VALUE_MIXED)
864
-			 */
865
-			if (!$this->isTyped(self::VALUE_MIXED, $currType)
866
-				&& ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) {
867
-				try {
868
-					$currType = $this->convertTypeToString($currType);
869
-					$type = $this->convertTypeToString($type);
870
-				} catch (AppConfigIncorrectTypeException) {
871
-					// can be ignored, this was just needed for a better exception message.
872
-				}
873
-				throw new AppConfigTypeConflictException('conflict between new type (' . $type . ') and old type (' . $currType . ')');
874
-			}
875
-
876
-			// we fix $type if the stored value, or the new value as it might be changed, is set as sensitive
877
-			if ($sensitive || $this->isTyped(self::VALUE_SENSITIVE, $currType)) {
878
-				$type |= self::VALUE_SENSITIVE;
879
-			}
880
-
881
-			try {
882
-				if ($lazy !== $this->isLazy($app, $key)) {
883
-					$refreshCache = true;
884
-				}
885
-			} catch (AppConfigUnknownKeyException) {
886
-				// pass
887
-			}
888
-
889
-			$update = $this->connection->getQueryBuilder();
890
-			$update->update('appconfig')
891
-				->set('configvalue', $update->createNamedParameter($value))
892
-				->set('lazy', $update->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
893
-				->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
894
-				->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
895
-				->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
896
-
897
-			$update->executeStatement();
898
-		}
899
-
900
-		if ($refreshCache) {
901
-			$this->clearCache();
902
-			return true;
903
-		}
904
-
905
-		// update local cache
906
-		if ($lazy) {
907
-			$this->lazyCache[$app][$key] = $value;
908
-		} else {
909
-			$this->fastCache[$app][$key] = $value;
910
-		}
911
-		$this->valueTypes[$app][$key] = $type;
912
-		$this->clearLocalCache();
913
-
914
-		return true;
915
-	}
916
-
917
-	/**
918
-	 * Change the type of config value.
919
-	 *
920
-	 * **WARNING:** Method is internal and **MUST** not be used as it may break things.
921
-	 *
922
-	 * @param string $app id of the app
923
-	 * @param string $key config key
924
-	 * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
925
-	 *
926
-	 * @return bool TRUE if database update were necessary
927
-	 * @throws AppConfigUnknownKeyException if $key is now known in database
928
-	 * @throws AppConfigIncorrectTypeException if $type is not valid
929
-	 * @internal
930
-	 * @since 29.0.0
931
-	 */
932
-	public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool {
933
-		$this->assertParams($app, $key);
934
-		$this->loadConfig(lazy: true);
935
-		$this->matchAndApplyLexiconDefinition($app, $key);
936
-		$this->isLazy($app, $key); // confirm key exists
937
-
938
-		// type can only be one type
939
-		if (!in_array($type, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
940
-			throw new AppConfigIncorrectTypeException('Unknown value type');
941
-		}
942
-
943
-		$currType = $this->valueTypes[$app][$key];
944
-		if (($type | self::VALUE_SENSITIVE) === ($currType | self::VALUE_SENSITIVE)) {
945
-			return false;
946
-		}
947
-
948
-		// we complete with sensitive flag if the stored value is set as sensitive
949
-		if ($this->isTyped(self::VALUE_SENSITIVE, $currType)) {
950
-			$type = $type | self::VALUE_SENSITIVE;
951
-		}
952
-
953
-		$update = $this->connection->getQueryBuilder();
954
-		$update->update('appconfig')
955
-			->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
956
-			->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
957
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
958
-		$update->executeStatement();
959
-		$this->valueTypes[$app][$key] = $type;
960
-
961
-		return true;
962
-	}
963
-
964
-
965
-	/**
966
-	 * @inheritDoc
967
-	 *
968
-	 * @param string $app id of the app
969
-	 * @param string $key config key
970
-	 * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
971
-	 *
972
-	 * @return bool TRUE if entry was found in database and an update was necessary
973
-	 * @since 29.0.0
974
-	 */
975
-	public function updateSensitive(string $app, string $key, bool $sensitive): bool {
976
-		$this->assertParams($app, $key);
977
-		$this->loadConfig(lazy: true);
978
-		$this->matchAndApplyLexiconDefinition($app, $key);
979
-
980
-		try {
981
-			if ($sensitive === $this->isSensitive($app, $key, null)) {
982
-				return false;
983
-			}
984
-		} catch (AppConfigUnknownKeyException $e) {
985
-			return false;
986
-		}
987
-
988
-		$lazy = $this->isLazy($app, $key);
989
-		if ($lazy) {
990
-			$cache = $this->lazyCache;
991
-		} else {
992
-			$cache = $this->fastCache;
993
-		}
994
-
995
-		if (!isset($cache[$app][$key])) {
996
-			throw new AppConfigUnknownKeyException('unknown config key');
997
-		}
998
-
999
-		/**
1000
-		 * type returned by getValueType() is already cleaned from sensitive flag
1001
-		 * we just need to update it based on $sensitive and store it in database
1002
-		 */
1003
-		$type = $this->getValueType($app, $key);
1004
-		$value = $cache[$app][$key];
1005
-		if ($sensitive) {
1006
-			$type |= self::VALUE_SENSITIVE;
1007
-			$value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
1008
-		} else {
1009
-			$value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
1010
-		}
1011
-
1012
-		$update = $this->connection->getQueryBuilder();
1013
-		$update->update('appconfig')
1014
-			->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
1015
-			->set('configvalue', $update->createNamedParameter($value))
1016
-			->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
1017
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1018
-		$update->executeStatement();
1019
-
1020
-		$this->valueTypes[$app][$key] = $type;
1021
-
1022
-		return true;
1023
-	}
1024
-
1025
-	/**
1026
-	 * @inheritDoc
1027
-	 *
1028
-	 * @param string $app id of the app
1029
-	 * @param string $key config key
1030
-	 * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
1031
-	 *
1032
-	 * @return bool TRUE if entry was found in database and an update was necessary
1033
-	 * @since 29.0.0
1034
-	 */
1035
-	public function updateLazy(string $app, string $key, bool $lazy): bool {
1036
-		$this->assertParams($app, $key);
1037
-		$this->loadConfig(lazy: true);
1038
-		$this->matchAndApplyLexiconDefinition($app, $key);
1039
-
1040
-		try {
1041
-			if ($lazy === $this->isLazy($app, $key)) {
1042
-				return false;
1043
-			}
1044
-		} catch (AppConfigUnknownKeyException $e) {
1045
-			return false;
1046
-		}
1047
-
1048
-		$update = $this->connection->getQueryBuilder();
1049
-		$update->update('appconfig')
1050
-			->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
1051
-			->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
1052
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1053
-		$update->executeStatement();
1054
-
1055
-		// At this point, it is a lot safer to clean cache
1056
-		$this->clearCache();
1057
-
1058
-		return true;
1059
-	}
1060
-
1061
-	/**
1062
-	 * @inheritDoc
1063
-	 *
1064
-	 * @param string $app id of the app
1065
-	 * @param string $key config key
1066
-	 *
1067
-	 * @return array
1068
-	 * @throws AppConfigUnknownKeyException if config key is not known in database
1069
-	 * @since 29.0.0
1070
-	 */
1071
-	public function getDetails(string $app, string $key): array {
1072
-		$this->assertParams($app, $key);
1073
-		$this->loadConfig(lazy: true);
1074
-		$this->matchAndApplyLexiconDefinition($app, $key);
1075
-		$lazy = $this->isLazy($app, $key);
1076
-
1077
-		if ($lazy) {
1078
-			$cache = $this->lazyCache;
1079
-		} else {
1080
-			$cache = $this->fastCache;
1081
-		}
1082
-
1083
-		$type = $this->getValueType($app, $key);
1084
-		try {
1085
-			$typeString = $this->convertTypeToString($type);
1086
-		} catch (AppConfigIncorrectTypeException $e) {
1087
-			$this->logger->warning('type stored in database is not correct', ['exception' => $e, 'type' => $type]);
1088
-			$typeString = (string)$type;
1089
-		}
1090
-
1091
-		if (!isset($cache[$app][$key])) {
1092
-			throw new AppConfigUnknownKeyException('unknown config key');
1093
-		}
1094
-
1095
-		$value = $cache[$app][$key];
1096
-		$sensitive = $this->isSensitive($app, $key, null);
1097
-		if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
1098
-			$value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
1099
-		}
1100
-
1101
-		return [
1102
-			'app' => $app,
1103
-			'key' => $key,
1104
-			'value' => $value,
1105
-			'type' => $type,
1106
-			'lazy' => $lazy,
1107
-			'typeString' => $typeString,
1108
-			'sensitive' => $sensitive
1109
-		];
1110
-	}
1111
-
1112
-	/**
1113
-	 * @inheritDoc
1114
-	 *
1115
-	 * @param string $app id of the app
1116
-	 * @param string $key config key
1117
-	 *
1118
-	 * @return array{app: string, key: string, lazy?: bool, valueType?: ValueType, valueTypeName?: string, sensitive?: bool, internal?: bool, default?: string, definition?: string, note?: string}
1119
-	 * @since 32.0.0
1120
-	 */
1121
-	public function getKeyDetails(string $app, string $key): array {
1122
-		$this->assertParams($app, $key);
1123
-		try {
1124
-			$details = $this->getDetails($app, $key);
1125
-		} catch (AppConfigUnknownKeyException) {
1126
-			$details = [
1127
-				'app' => $app,
1128
-				'key' => $key
1129
-			];
1130
-		}
1131
-
1132
-		/** @var Entry $lexiconEntry */
1133
-		try {
1134
-			$lazy = false;
1135
-			$this->matchAndApplyLexiconDefinition($app, $key, $lazy, lexiconEntry: $lexiconEntry);
1136
-		} catch (AppConfigTypeConflictException|AppConfigUnknownKeyException) {
1137
-			// can be ignored
1138
-		}
1139
-
1140
-		if ($lexiconEntry !== null) {
1141
-			$details = array_merge($details, [
1142
-				'lazy' => $lexiconEntry->isLazy(),
1143
-				'valueType' => $lexiconEntry->getValueType(),
1144
-				'valueTypeName' => $lexiconEntry->getValueType()->name,
1145
-				'sensitive' => $lexiconEntry->isFlagged(self::FLAG_SENSITIVE),
1146
-				'internal' => $lexiconEntry->isFlagged(self::FLAG_INTERNAL),
1147
-				'default' => $lexiconEntry->getDefault($this->presetManager->getLexiconPreset()),
1148
-				'definition' => $lexiconEntry->getDefinition(),
1149
-				'note' => $lexiconEntry->getNote(),
1150
-			]);
1151
-		}
1152
-
1153
-		return array_filter($details, static fn ($v): bool => ($v !== null));
1154
-	}
1155
-
1156
-	/**
1157
-	 * @param string $type
1158
-	 *
1159
-	 * @return int
1160
-	 * @throws AppConfigIncorrectTypeException
1161
-	 * @since 29.0.0
1162
-	 */
1163
-	public function convertTypeToInt(string $type): int {
1164
-		return match (strtolower($type)) {
1165
-			'mixed' => IAppConfig::VALUE_MIXED,
1166
-			'string' => IAppConfig::VALUE_STRING,
1167
-			'integer' => IAppConfig::VALUE_INT,
1168
-			'float' => IAppConfig::VALUE_FLOAT,
1169
-			'boolean' => IAppConfig::VALUE_BOOL,
1170
-			'array' => IAppConfig::VALUE_ARRAY,
1171
-			default => throw new AppConfigIncorrectTypeException('Unknown type ' . $type)
1172
-		};
1173
-	}
1174
-
1175
-	/**
1176
-	 * @param int $type
1177
-	 *
1178
-	 * @return string
1179
-	 * @throws AppConfigIncorrectTypeException
1180
-	 * @since 29.0.0
1181
-	 */
1182
-	public function convertTypeToString(int $type): string {
1183
-		$type &= ~self::VALUE_SENSITIVE;
1184
-
1185
-		return match ($type) {
1186
-			IAppConfig::VALUE_MIXED => 'mixed',
1187
-			IAppConfig::VALUE_STRING => 'string',
1188
-			IAppConfig::VALUE_INT => 'integer',
1189
-			IAppConfig::VALUE_FLOAT => 'float',
1190
-			IAppConfig::VALUE_BOOL => 'boolean',
1191
-			IAppConfig::VALUE_ARRAY => 'array',
1192
-			default => throw new AppConfigIncorrectTypeException('Unknown numeric type ' . $type)
1193
-		};
1194
-	}
1195
-
1196
-	/**
1197
-	 * @inheritDoc
1198
-	 *
1199
-	 * @param string $app id of the app
1200
-	 * @param string $key config key
1201
-	 *
1202
-	 * @since 29.0.0
1203
-	 */
1204
-	public function deleteKey(string $app, string $key): void {
1205
-		$this->assertParams($app, $key);
1206
-		$this->matchAndApplyLexiconDefinition($app, $key);
1207
-
1208
-		$qb = $this->connection->getQueryBuilder();
1209
-		$qb->delete('appconfig')
1210
-			->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
1211
-			->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
1212
-		$qb->executeStatement();
1213
-
1214
-		unset($this->lazyCache[$app][$key]);
1215
-		unset($this->fastCache[$app][$key]);
1216
-		unset($this->valueTypes[$app][$key]);
1217
-		$this->clearLocalCache();
1218
-	}
1219
-
1220
-	/**
1221
-	 * @inheritDoc
1222
-	 *
1223
-	 * @param string $app id of the app
1224
-	 *
1225
-	 * @since 29.0.0
1226
-	 */
1227
-	public function deleteApp(string $app): void {
1228
-		$this->assertParams($app);
1229
-		$qb = $this->connection->getQueryBuilder();
1230
-		$qb->delete('appconfig')
1231
-			->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
1232
-		$qb->executeStatement();
1233
-
1234
-		$this->clearCache();
1235
-	}
1236
-
1237
-	/**
1238
-	 * @inheritDoc
1239
-	 *
1240
-	 * @param bool $reload set to TRUE to refill cache instantly after clearing it
1241
-	 *
1242
-	 * @internal
1243
-	 * @since 29.0.0
1244
-	 */
1245
-	public function clearCache(bool $reload = false): void {
1246
-		$this->lazyLoaded = $this->fastLoaded = false;
1247
-		$this->lazyCache = $this->fastCache = $this->valueTypes = $this->configLexiconDetails = [];
1248
-		$this->localCache?->remove(self::LOCAL_CACHE_KEY);
1249
-
1250
-		if (!$reload) {
1251
-			return;
1252
-		}
1253
-
1254
-		$this->loadConfig(lazy: true);
1255
-	}
1256
-
1257
-
1258
-	/**
1259
-	 * For debug purpose.
1260
-	 * Returns the cached data.
1261
-	 *
1262
-	 * @return array
1263
-	 * @since 29.0.0
1264
-	 * @internal
1265
-	 */
1266
-	public function statusCache(): array {
1267
-		return [
1268
-			'fastLoaded' => $this->fastLoaded,
1269
-			'fastCache' => $this->fastCache,
1270
-			'lazyLoaded' => $this->lazyLoaded,
1271
-			'lazyCache' => $this->lazyCache,
1272
-		];
1273
-	}
1274
-
1275
-	/**
1276
-	 * @param int $needle bitflag to search
1277
-	 * @param int $type known value
1278
-	 *
1279
-	 * @return bool TRUE if bitflag $needle is set in $type
1280
-	 */
1281
-	private function isTyped(int $needle, int $type): bool {
1282
-		return (($needle & $type) !== 0);
1283
-	}
1284
-
1285
-	/**
1286
-	 * Confirm the string set for app and key fit the database description
1287
-	 *
1288
-	 * @param string $app assert $app fit in database
1289
-	 * @param string $configKey assert config key fit in database
1290
-	 * @param bool $allowEmptyApp $app can be empty string
1291
-	 * @param int $valueType assert value type is only one type
1292
-	 *
1293
-	 * @throws InvalidArgumentException
1294
-	 */
1295
-	private function assertParams(string $app = '', string $configKey = '', bool $allowEmptyApp = false, int $valueType = -1): void {
1296
-		if (!$allowEmptyApp && $app === '') {
1297
-			throw new InvalidArgumentException('app cannot be an empty string');
1298
-		}
1299
-		if (strlen($app) > self::APP_MAX_LENGTH) {
1300
-			throw new InvalidArgumentException(
1301
-				'Value (' . $app . ') for app is too long (' . self::APP_MAX_LENGTH . ')'
1302
-			);
1303
-		}
1304
-		if (strlen($configKey) > self::KEY_MAX_LENGTH) {
1305
-			throw new InvalidArgumentException('Value (' . $configKey . ') for key is too long (' . self::KEY_MAX_LENGTH . ')');
1306
-		}
1307
-		if ($valueType > -1) {
1308
-			$valueType &= ~self::VALUE_SENSITIVE;
1309
-			if (!in_array($valueType, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
1310
-				throw new InvalidArgumentException('Unknown value type');
1311
-			}
1312
-		}
1313
-	}
1314
-
1315
-	/**
1316
-	 * Load normal config or config set as lazy loaded
1317
-	 *
1318
-	 * @param bool $lazy set to TRUE to also load config values set as lazy loaded
1319
-	 */
1320
-	private function loadConfig(?string $app = null, bool $lazy = false): void {
1321
-		if ($this->isLoaded($lazy)) {
1322
-			return;
1323
-		}
1324
-
1325
-		// if lazy is null or true, we debug log
1326
-		if ($lazy === true && $app !== null) {
1327
-			$exception = new \RuntimeException('The loading of lazy AppConfig values have been triggered by app "' . $app . '"');
1328
-			$this->logger->debug($exception->getMessage(), ['exception' => $exception, 'app' => $app]);
1329
-		}
1330
-
1331
-		$loadLazyOnly = $lazy && $this->isLoaded();
1332
-
1333
-		/** @var array<mixed> */
1334
-		$cacheContent = $this->localCache?->get(self::LOCAL_CACHE_KEY) ?? [];
1335
-		$includesLazyValues = !empty($cacheContent) && !empty($cacheContent['lazyCache']);
1336
-		if (!empty($cacheContent) && (!$lazy || $includesLazyValues)) {
1337
-			$this->valueTypes = $cacheContent['valueTypes'];
1338
-			$this->fastCache = $cacheContent['fastCache'];
1339
-			$this->fastLoaded = !empty($this->fastCache);
1340
-			if ($includesLazyValues) {
1341
-				$this->lazyCache = $cacheContent['lazyCache'];
1342
-				$this->lazyLoaded = !empty($this->lazyCache);
1343
-			}
1344
-			return;
1345
-		}
1346
-
1347
-		// Otherwise no cache available and we need to fetch from database
1348
-		$qb = $this->connection->getQueryBuilder();
1349
-		$qb->from('appconfig')
1350
-			->select('appid', 'configkey', 'configvalue', 'type');
1351
-
1352
-		if ($lazy === false) {
1353
-			$qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
1354
-		} else {
1355
-			if ($loadLazyOnly) {
1356
-				$qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)));
1357
-			}
1358
-			$qb->addSelect('lazy');
1359
-		}
1360
-
1361
-		$result = $qb->executeQuery();
1362
-		$rows = $result->fetchAll();
1363
-		foreach ($rows as $row) {
1364
-			// most of the time, 'lazy' is not in the select because its value is already known
1365
-			if ($lazy && ((int)$row['lazy']) === 1) {
1366
-				$this->lazyCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1367
-			} else {
1368
-				$this->fastCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1369
-			}
1370
-			$this->valueTypes[$row['appid']][$row['configkey']] = (int)($row['type'] ?? 0);
1371
-		}
1372
-
1373
-		$result->closeCursor();
1374
-		$this->localCache?->set(
1375
-			self::LOCAL_CACHE_KEY,
1376
-			[
1377
-				'fastCache' => $this->fastCache,
1378
-				'lazyCache' => $this->lazyCache,
1379
-				'valueTypes' => $this->valueTypes,
1380
-			],
1381
-			self::LOCAL_CACHE_TTL,
1382
-		);
1383
-
1384
-		$this->fastLoaded = true;
1385
-		$this->lazyLoaded = $lazy;
1386
-	}
1387
-
1388
-	/**
1389
-	 * @param bool $lazy - If set to true then also check if lazy values are loaded
1390
-	 */
1391
-	private function isLoaded(bool $lazy = false): bool {
1392
-		return $this->fastLoaded && (!$lazy || $this->lazyLoaded);
1393
-	}
1394
-
1395
-	/**
1396
-	 * Gets the config value
1397
-	 *
1398
-	 * @param string $app app
1399
-	 * @param string $key key
1400
-	 * @param string $default - Default value if the key does not exist
1401
-	 *
1402
-	 * @return string the value or $default
1403
-	 * @deprecated 29.0.0 use getValue*()
1404
-	 *
1405
-	 * This function gets a value from the appconfig table. If the key does
1406
-	 * not exist the default value will be returned
1407
-	 */
1408
-	public function getValue($app, $key, $default = '') {
1409
-		$this->loadConfig($app);
1410
-		$this->matchAndApplyLexiconDefinition($app, $key);
1411
-
1412
-		return $this->fastCache[$app][$key] ?? $default;
1413
-	}
1414
-
1415
-	/**
1416
-	 * Sets a value. If the key did not exist before it will be created.
1417
-	 *
1418
-	 * @param string $app app
1419
-	 * @param string $key key
1420
-	 * @param string|float|int $value value
1421
-	 *
1422
-	 * @return bool True if the value was inserted or updated, false if the value was the same
1423
-	 * @throws AppConfigTypeConflictException
1424
-	 * @throws AppConfigUnknownKeyException
1425
-	 * @deprecated 29.0.0
1426
-	 */
1427
-	public function setValue($app, $key, $value) {
1428
-		/**
1429
-		 * TODO: would it be overkill, or decently improve performance, to catch
1430
-		 * call to this method with $key='enabled' and 'hide' config value related
1431
-		 * to $app when the app is disabled (by modifying entry in database: lazy=lazy+2)
1432
-		 * or enabled (lazy=lazy-2)
1433
-		 *
1434
-		 * this solution would remove the loading of config values from disabled app
1435
-		 * unless calling the method.
1436
-		 */
1437
-		return $this->setTypedValue($app, $key, (string)$value, false, self::VALUE_MIXED);
1438
-	}
1439
-
1440
-
1441
-	/**
1442
-	 * get multiple values, either the app or key can be used as wildcard by setting it to false
1443
-	 *
1444
-	 * @param string|false $app
1445
-	 * @param string|false $key
1446
-	 *
1447
-	 * @return array|false
1448
-	 * @deprecated 29.0.0 use {@see getAllValues()}
1449
-	 */
1450
-	public function getValues($app, $key) {
1451
-		if (($app !== false) === ($key !== false)) {
1452
-			return false;
1453
-		}
1454
-
1455
-		$key = ($key === false) ? '' : $key;
1456
-		if (!$app) {
1457
-			return $this->searchValues($key, false, self::VALUE_MIXED);
1458
-		} else {
1459
-			return $this->getAllValues($app, $key);
1460
-		}
1461
-	}
1462
-
1463
-	/**
1464
-	 * get all values of the app or and filters out sensitive data
1465
-	 *
1466
-	 * @param string $app
1467
-	 *
1468
-	 * @return array
1469
-	 * @deprecated 29.0.0 use {@see getAllValues()}
1470
-	 */
1471
-	public function getFilteredValues($app) {
1472
-		return $this->getAllValues($app, filtered: true);
1473
-	}
1474
-
1475
-
1476
-	/**
1477
-	 * **Warning:** avoid default NULL value for $lazy as this will
1478
-	 * load all lazy values from the database
1479
-	 *
1480
-	 * @param string $app
1481
-	 * @param array<string, string> $values ['key' => 'value']
1482
-	 * @param bool|null $lazy
1483
-	 *
1484
-	 * @return array<string, string|int|float|bool|array>
1485
-	 */
1486
-	private function formatAppValues(string $app, array $values, ?bool $lazy = null): array {
1487
-		foreach ($values as $key => $value) {
1488
-			try {
1489
-				$type = $this->getValueType($app, $key, $lazy);
1490
-			} catch (AppConfigUnknownKeyException) {
1491
-				continue;
1492
-			}
1493
-
1494
-			$values[$key] = $this->convertTypedValue($value, $type);
1495
-		}
1496
-
1497
-		return $values;
1498
-	}
1499
-
1500
-	/**
1501
-	 * convert string value to the expected type
1502
-	 *
1503
-	 * @param string $value
1504
-	 * @param int $type
1505
-	 *
1506
-	 * @return string|int|float|bool|array
1507
-	 */
1508
-	private function convertTypedValue(string $value, int $type): string|int|float|bool|array {
1509
-		switch ($type) {
1510
-			case self::VALUE_INT:
1511
-				return (int)$value;
1512
-			case self::VALUE_FLOAT:
1513
-				return (float)$value;
1514
-			case self::VALUE_BOOL:
1515
-				return in_array(strtolower($value), ['1', 'true', 'yes', 'on']);
1516
-			case self::VALUE_ARRAY:
1517
-				try {
1518
-					return json_decode($value, true, flags: JSON_THROW_ON_ERROR);
1519
-				} catch (JsonException $e) {
1520
-					// ignoreable
1521
-				}
1522
-				break;
1523
-		}
1524
-		return $value;
1525
-	}
1526
-
1527
-	/**
1528
-	 * @param string $app
1529
-	 *
1530
-	 * @return string[]
1531
-	 * @deprecated 29.0.0 data sensitivity should be set when calling setValue*()
1532
-	 */
1533
-	private function getSensitiveKeys(string $app): array {
1534
-		$sensitiveValues = [
1535
-			'circles' => [
1536
-				'/^key_pairs$/',
1537
-				'/^local_gskey$/',
1538
-			],
1539
-			'call_summary_bot' => [
1540
-				'/^secret_(.*)$/',
1541
-			],
1542
-			'external' => [
1543
-				'/^sites$/',
1544
-				'/^jwt_token_privkey_(.*)$/',
1545
-			],
1546
-			'globalsiteselector' => [
1547
-				'/^gss\.jwt\.key$/',
1548
-			],
1549
-			'gpgmailer' => [
1550
-				'/^GpgServerKey$/',
1551
-			],
1552
-			'integration_discourse' => [
1553
-				'/^private_key$/',
1554
-				'/^public_key$/',
1555
-			],
1556
-			'integration_dropbox' => [
1557
-				'/^client_id$/',
1558
-				'/^client_secret$/',
1559
-			],
1560
-			'integration_github' => [
1561
-				'/^client_id$/',
1562
-				'/^client_secret$/',
1563
-			],
1564
-			'integration_gitlab' => [
1565
-				'/^client_id$/',
1566
-				'/^client_secret$/',
1567
-				'/^oauth_instance_url$/',
1568
-			],
1569
-			'integration_google' => [
1570
-				'/^client_id$/',
1571
-				'/^client_secret$/',
1572
-			],
1573
-			'integration_jira' => [
1574
-				'/^client_id$/',
1575
-				'/^client_secret$/',
1576
-				'/^forced_instance_url$/',
1577
-			],
1578
-			'integration_onedrive' => [
1579
-				'/^client_id$/',
1580
-				'/^client_secret$/',
1581
-			],
1582
-			'integration_openproject' => [
1583
-				'/^client_id$/',
1584
-				'/^client_secret$/',
1585
-				'/^oauth_instance_url$/',
1586
-			],
1587
-			'integration_reddit' => [
1588
-				'/^client_id$/',
1589
-				'/^client_secret$/',
1590
-			],
1591
-			'integration_suitecrm' => [
1592
-				'/^client_id$/',
1593
-				'/^client_secret$/',
1594
-				'/^oauth_instance_url$/',
1595
-			],
1596
-			'integration_twitter' => [
1597
-				'/^consumer_key$/',
1598
-				'/^consumer_secret$/',
1599
-				'/^followed_user$/',
1600
-			],
1601
-			'integration_zammad' => [
1602
-				'/^client_id$/',
1603
-				'/^client_secret$/',
1604
-				'/^oauth_instance_url$/',
1605
-			],
1606
-			'maps' => [
1607
-				'/^mapboxAPIKEY$/',
1608
-			],
1609
-			'notify_push' => [
1610
-				'/^cookie$/',
1611
-			],
1612
-			'onlyoffice' => [
1613
-				'/^jwt_secret$/',
1614
-			],
1615
-			'passwords' => [
1616
-				'/^SSEv1ServerKey$/',
1617
-			],
1618
-			'serverinfo' => [
1619
-				'/^token$/',
1620
-			],
1621
-			'spreed' => [
1622
-				'/^bridge_bot_password$/',
1623
-				'/^hosted-signaling-server-(.*)$/',
1624
-				'/^recording_servers$/',
1625
-				'/^signaling_servers$/',
1626
-				'/^signaling_ticket_secret$/',
1627
-				'/^signaling_token_privkey_(.*)$/',
1628
-				'/^signaling_token_pubkey_(.*)$/',
1629
-				'/^sip_bridge_dialin_info$/',
1630
-				'/^sip_bridge_shared_secret$/',
1631
-				'/^stun_servers$/',
1632
-				'/^turn_servers$/',
1633
-				'/^turn_server_secret$/',
1634
-			],
1635
-			'support' => [
1636
-				'/^last_response$/',
1637
-				'/^potential_subscription_key$/',
1638
-				'/^subscription_key$/',
1639
-			],
1640
-			'theming' => [
1641
-				'/^imprintUrl$/',
1642
-				'/^privacyUrl$/',
1643
-				'/^slogan$/',
1644
-				'/^url$/',
1645
-			],
1646
-			'twofactor_gateway' => [
1647
-				'/^.*token$/',
1648
-			],
1649
-			'user_ldap' => [
1650
-				'/^(s..)?ldap_agent_password$/',
1651
-			],
1652
-			'user_saml' => [
1653
-				'/^idp-x509cert$/',
1654
-			],
1655
-			'whiteboard' => [
1656
-				'/^jwt_secret_key$/',
1657
-			],
1658
-		];
1659
-
1660
-		return $sensitiveValues[$app] ?? [];
1661
-	}
1662
-
1663
-	/**
1664
-	 * Clear all the cached app config values
1665
-	 * New cache will be generated next time a config value is retrieved
1666
-	 *
1667
-	 * @deprecated 29.0.0 use {@see clearCache()}
1668
-	 */
1669
-	public function clearCachedConfig(): void {
1670
-		$this->clearCache();
1671
-	}
1672
-
1673
-	/**
1674
-	 * Match and apply current use of config values with defined lexicon.
1675
-	 * Set $lazy to NULL only if only interested into checking that $key is alias.
1676
-	 *
1677
-	 * @throws AppConfigUnknownKeyException
1678
-	 * @throws AppConfigTypeConflictException
1679
-	 * @return bool TRUE if everything is fine compared to lexicon or lexicon does not exist
1680
-	 */
1681
-	private function matchAndApplyLexiconDefinition(
1682
-		string $app,
1683
-		string &$key,
1684
-		?bool &$lazy = null,
1685
-		int &$type = self::VALUE_MIXED,
1686
-		?string &$default = null,
1687
-		?Entry &$lexiconEntry = null,
1688
-	): bool {
1689
-		if (in_array($key,
1690
-			[
1691
-				'enabled',
1692
-				'installed_version',
1693
-				'types',
1694
-			])) {
1695
-			return true; // we don't break stuff for this list of config keys.
1696
-		}
1697
-		$configDetails = $this->getConfigDetailsFromLexicon($app);
1698
-		if (array_key_exists($key, $configDetails['aliases']) && !$this->ignoreLexiconAliases) {
1699
-			// in case '$rename' is set in ConfigLexiconEntry, we use the new config key
1700
-			$key = $configDetails['aliases'][$key];
1701
-		}
1702
-
1703
-		if (!array_key_exists($key, $configDetails['entries'])) {
1704
-			return $this->applyLexiconStrictness($configDetails['strictness'], $app . '/' . $key);
1705
-		}
1706
-
1707
-		// if lazy is NULL, we ignore all check on the type/lazyness/default from Lexicon
1708
-		if ($lazy === null) {
1709
-			return true;
1710
-		}
1711
-
1712
-		/** @var Entry $lexiconEntry */
1713
-		$lexiconEntry = $configDetails['entries'][$key];
1714
-		$type &= ~self::VALUE_SENSITIVE;
1715
-
1716
-		$appConfigValueType = $lexiconEntry->getValueType()->toAppConfigFlag();
1717
-		if ($type === self::VALUE_MIXED) {
1718
-			$type = $appConfigValueType; // we overwrite if value was requested as mixed
1719
-		} elseif ($appConfigValueType !== $type) {
1720
-			throw new AppConfigTypeConflictException('The app config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
1721
-		}
1722
-
1723
-		$lazy = $lexiconEntry->isLazy();
1724
-		// only look for default if needed, default from Lexicon got priority
1725
-		if ($default !== null) {
1726
-			$default = $lexiconEntry->getDefault($this->presetManager->getLexiconPreset()) ?? $default;
1727
-		}
1728
-
1729
-		if ($lexiconEntry->isFlagged(self::FLAG_SENSITIVE)) {
1730
-			$type |= self::VALUE_SENSITIVE;
1731
-		}
1732
-		if ($lexiconEntry->isDeprecated()) {
1733
-			$this->logger->notice('App config key ' . $app . '/' . $key . ' is set as deprecated.');
1734
-		}
1735
-
1736
-		return true;
1737
-	}
1738
-
1739
-	/**
1740
-	 * manage ConfigLexicon behavior based on strictness set in IConfigLexicon
1741
-	 *
1742
-	 * @param Strictness|null $strictness
1743
-	 * @param string $line
1744
-	 *
1745
-	 * @return bool TRUE if conflict can be fully ignored, FALSE if action should be not performed
1746
-	 * @throws AppConfigUnknownKeyException if strictness implies exception
1747
-	 * @see \OCP\Config\Lexicon\ILexicon::getStrictness()
1748
-	 */
1749
-	private function applyLexiconStrictness(?Strictness $strictness, string $configAppKey): bool {
1750
-		if ($strictness === null) {
1751
-			return true;
1752
-		}
1753
-
1754
-		$line = 'The app config key ' . $configAppKey . ' is not defined in the config lexicon';
1755
-		switch ($strictness) {
1756
-			case Strictness::IGNORE:
1757
-				return true;
1758
-			case Strictness::NOTICE:
1759
-				if (!in_array($configAppKey, $this->strictnessApplied, true)) {
1760
-					$this->strictnessApplied[] = $configAppKey;
1761
-					$this->logger->notice($line);
1762
-				}
1763
-				return true;
1764
-			case Strictness::WARNING:
1765
-				if (!in_array($configAppKey, $this->strictnessApplied, true)) {
1766
-					$this->strictnessApplied[] = $configAppKey;
1767
-					$this->logger->warning($line);
1768
-				}
1769
-				return false;
1770
-		}
1771
-
1772
-		throw new AppConfigUnknownKeyException($line);
1773
-	}
1774
-
1775
-	/**
1776
-	 * extract details from registered $appId's config lexicon
1777
-	 *
1778
-	 * @param string $appId
1779
-	 * @internal
1780
-	 *
1781
-	 * @return array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}
1782
-	 */
1783
-	public function getConfigDetailsFromLexicon(string $appId): array {
1784
-		if (!array_key_exists($appId, $this->configLexiconDetails)) {
1785
-			$entries = $aliases = [];
1786
-			$bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
1787
-			$configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId);
1788
-			foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) {
1789
-				$entries[$configEntry->getKey()] = $configEntry;
1790
-				$newName = $configEntry->getRename();
1791
-				if ($newName !== null) {
1792
-					$aliases[$newName] = $configEntry->getKey();
1793
-				}
1794
-			}
1795
-
1796
-			$this->configLexiconDetails[$appId] = [
1797
-				'entries' => $entries,
1798
-				'aliases' => $aliases,
1799
-				'strictness' => $configLexicon?->getStrictness() ?? Strictness::IGNORE
1800
-			];
1801
-		}
1802
-
1803
-		return $this->configLexiconDetails[$appId];
1804
-	}
1805
-
1806
-	/**
1807
-	 * get Lexicon Entry using appId and config key entry
1808
-	 *
1809
-	 * @return Entry|null NULL if entry does not exist in app's Lexicon
1810
-	 * @internal
1811
-	 */
1812
-	public function getLexiconEntry(string $appId, string $key): ?Entry {
1813
-		return $this->getConfigDetailsFromLexicon($appId)['entries'][$key] ?? null;
1814
-	}
1815
-
1816
-	/**
1817
-	 * if set to TRUE, ignore aliases defined in Config Lexicon during the use of the methods of this class
1818
-	 *
1819
-	 * @internal
1820
-	 */
1821
-	public function ignoreLexiconAliases(bool $ignore): void {
1822
-		$this->ignoreLexiconAliases = $ignore;
1823
-	}
1824
-
1825
-	/**
1826
-	 * Returns the installed versions of all apps
1827
-	 *
1828
-	 * @return array<string, string>
1829
-	 */
1830
-	public function getAppInstalledVersions(bool $onlyEnabled = false): array {
1831
-		if ($this->appVersionsCache === null) {
1832
-			/** @var array<string, string> */
1833
-			$this->appVersionsCache = $this->searchValues('installed_version', false, IAppConfig::VALUE_STRING);
1834
-		}
1835
-		if ($onlyEnabled) {
1836
-			return array_filter(
1837
-				$this->appVersionsCache,
1838
-				fn (string $app): bool => $this->getValueString($app, 'enabled', 'no') !== 'no',
1839
-				ARRAY_FILTER_USE_KEY
1840
-			);
1841
-		}
1842
-		return $this->appVersionsCache;
1843
-	}
1844
-
1845
-	private function clearLocalCache(): void {
1846
-		$this->localCache?->remove(self::LOCAL_CACHE_KEY);
1847
-	}
54
+    private const APP_MAX_LENGTH = 32;
55
+    private const KEY_MAX_LENGTH = 64;
56
+    private const ENCRYPTION_PREFIX = '$AppConfigEncryption$';
57
+    private const ENCRYPTION_PREFIX_LENGTH = 21; // strlen(self::ENCRYPTION_PREFIX)
58
+    private const LOCAL_CACHE_KEY = 'OC\\AppConfig';
59
+    private const LOCAL_CACHE_TTL = 3;
60
+
61
+    /** @var array<string, array<string, string>> ['app_id' => ['config_key' => 'config_value']] */
62
+    private array $fastCache = [];   // cache for normal config keys
63
+    /** @var array<string, array<string, string>> ['app_id' => ['config_key' => 'config_value']] */
64
+    private array $lazyCache = [];   // cache for lazy config keys
65
+    /** @var array<string, array<string, int>> ['app_id' => ['config_key' => bitflag]] */
66
+    private array $valueTypes = [];  // type for all config values
67
+    private bool $fastLoaded = false;
68
+    private bool $lazyLoaded = false;
69
+    /** @var array<string, array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
70
+    private array $configLexiconDetails = [];
71
+    private bool $ignoreLexiconAliases = false;
72
+    private array $strictnessApplied = [];
73
+
74
+    /** @var ?array<string, string> */
75
+    private ?array $appVersionsCache = null;
76
+    private ?ICache $localCache = null;
77
+
78
+    public function __construct(
79
+        protected IDBConnection $connection,
80
+        protected IConfig $config,
81
+        private readonly ConfigManager $configManager,
82
+        private readonly PresetManager $presetManager,
83
+        protected LoggerInterface $logger,
84
+        protected ICrypto $crypto,
85
+        public readonly CacheFactory $cacheFactory,
86
+    ) {
87
+        if ($config->getSystemValueBool('cache_app_config', true) && $cacheFactory->isLocalCacheAvailable()) {
88
+            $cacheFactory->withServerVersionPrefix(function (ICacheFactory $factory) {
89
+                $this->localCache = $factory->createLocal();
90
+            });
91
+        }
92
+    }
93
+
94
+    /**
95
+     * @inheritDoc
96
+     *
97
+     * @return list<string> list of app ids
98
+     * @since 7.0.0
99
+     */
100
+    public function getApps(): array {
101
+        $this->loadConfig(lazy: true);
102
+        $apps = array_merge(array_keys($this->fastCache), array_keys($this->lazyCache));
103
+        sort($apps);
104
+
105
+        return array_values(array_unique($apps));
106
+    }
107
+
108
+    /**
109
+     * @inheritDoc
110
+     *
111
+     * @param string $app id of the app
112
+     * @return list<string> list of stored config keys
113
+     * @see searchKeys to not load lazy config keys
114
+     *
115
+     * @since 29.0.0
116
+     */
117
+    public function getKeys(string $app): array {
118
+        $this->assertParams($app);
119
+        $this->loadConfig($app, true);
120
+        $keys = array_merge(array_keys($this->fastCache[$app] ?? []), array_keys($this->lazyCache[$app] ?? []));
121
+        sort($keys);
122
+
123
+        return array_values(array_unique($keys));
124
+    }
125
+
126
+    /**
127
+     * @inheritDoc
128
+     *
129
+     * @param string $app id of the app
130
+     * @param string $prefix returns only keys starting with this value
131
+     * @param bool $lazy TRUE to search in lazy config keys
132
+     * @return list<string> list of stored config keys
133
+     * @since 32.0.0
134
+     */
135
+    public function searchKeys(string $app, string $prefix = '', bool $lazy = false): array {
136
+        $this->assertParams($app);
137
+        $this->loadConfig($app, $lazy);
138
+        if ($lazy) {
139
+            $keys = array_keys($this->lazyCache[$app] ?? []);
140
+        } else {
141
+            $keys = array_keys($this->fastCache[$app] ?? []);
142
+        }
143
+
144
+        if ($prefix !== '') {
145
+            $keys = array_filter($keys, static fn (string $key): bool => str_starts_with($key, $prefix));
146
+        }
147
+
148
+        sort($keys);
149
+        return array_values(array_unique($keys));
150
+    }
151
+
152
+    /**
153
+     * @inheritDoc
154
+     *
155
+     * @param string $app id of the app
156
+     * @param string $key config key
157
+     * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
158
+     *
159
+     * @return bool TRUE if key exists
160
+     * @since 7.0.0
161
+     * @since 29.0.0 Added the $lazy argument
162
+     */
163
+    public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
164
+        $this->assertParams($app, $key);
165
+        $this->loadConfig($app, $lazy ?? true);
166
+        $this->matchAndApplyLexiconDefinition($app, $key);
167
+
168
+        $hasLazy = isset($this->lazyCache[$app][$key]);
169
+        $hasFast = isset($this->fastCache[$app][$key]);
170
+        if ($lazy === null) {
171
+            return $hasLazy || $hasFast;
172
+        } else {
173
+            return $lazy ? $hasLazy : $hasFast;
174
+        }
175
+    }
176
+
177
+    /**
178
+     * @param string $app id of the app
179
+     * @param string $key config key
180
+     * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
181
+     *
182
+     * @return bool
183
+     * @throws AppConfigUnknownKeyException if config key is not known
184
+     * @since 29.0.0
185
+     */
186
+    public function isSensitive(string $app, string $key, ?bool $lazy = false): bool {
187
+        $this->assertParams($app, $key);
188
+        $this->loadConfig(null, $lazy ?? true);
189
+        $this->matchAndApplyLexiconDefinition($app, $key);
190
+
191
+        if (!isset($this->valueTypes[$app][$key])) {
192
+            throw new AppConfigUnknownKeyException('unknown config key');
193
+        }
194
+
195
+        return $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key]);
196
+    }
197
+
198
+    /**
199
+     * @inheritDoc
200
+     *
201
+     * @param string $app if of the app
202
+     * @param string $key config key
203
+     *
204
+     * @return bool TRUE if config is lazy loaded
205
+     * @throws AppConfigUnknownKeyException if config key is not known
206
+     * @see IAppConfig for details about lazy loading
207
+     * @since 29.0.0
208
+     */
209
+    public function isLazy(string $app, string $key): bool {
210
+        $this->assertParams($app, $key);
211
+        $this->matchAndApplyLexiconDefinition($app, $key);
212
+
213
+        // there is a huge probability the non-lazy config are already loaded
214
+        if ($this->hasKey($app, $key, false)) {
215
+            return false;
216
+        }
217
+
218
+        // key not found, we search in the lazy config
219
+        if ($this->hasKey($app, $key, true)) {
220
+            return true;
221
+        }
222
+
223
+        throw new AppConfigUnknownKeyException('unknown config key');
224
+    }
225
+
226
+
227
+    /**
228
+     * @inheritDoc
229
+     *
230
+     * @param string $app id of the app
231
+     * @param string $prefix config keys prefix to search
232
+     * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
233
+     *
234
+     * @return array<string, string|int|float|bool|array> [configKey => configValue]
235
+     * @since 29.0.0
236
+     */
237
+    public function getAllValues(string $app, string $prefix = '', bool $filtered = false): array {
238
+        $this->assertParams($app, $prefix);
239
+        // if we want to filter values, we need to get sensitivity
240
+        $this->loadConfig($app, true);
241
+        // array_merge() will remove numeric keys (here config keys), so addition arrays instead
242
+        $values = $this->formatAppValues($app, ($this->fastCache[$app] ?? []) + ($this->lazyCache[$app] ?? []));
243
+        $values = array_filter(
244
+            $values,
245
+            function (string $key) use ($prefix): bool {
246
+                return str_starts_with($key, $prefix); // filter values based on $prefix
247
+            }, ARRAY_FILTER_USE_KEY
248
+        );
249
+
250
+        if (!$filtered) {
251
+            return $values;
252
+        }
253
+
254
+        /**
255
+         * Using the old (deprecated) list of sensitive values.
256
+         */
257
+        foreach ($this->getSensitiveKeys($app) as $sensitiveKeyExp) {
258
+            $sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
259
+            foreach ($sensitiveKeys as $sensitiveKey) {
260
+                $this->valueTypes[$app][$sensitiveKey] = ($this->valueTypes[$app][$sensitiveKey] ?? 0) | self::VALUE_SENSITIVE;
261
+            }
262
+        }
263
+
264
+        $result = [];
265
+        foreach ($values as $key => $value) {
266
+            $result[$key] = $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key] ?? 0) ? IConfig::SENSITIVE_VALUE : $value;
267
+        }
268
+
269
+        return $result;
270
+    }
271
+
272
+    /**
273
+     * @inheritDoc
274
+     *
275
+     * @param string $key config key
276
+     * @param bool $lazy search within lazy loaded config
277
+     * @param int|null $typedAs enforce type for the returned values ({@see self::VALUE_STRING} and others)
278
+     *
279
+     * @return array<string, string|int|float|bool|array> [appId => configValue]
280
+     * @since 29.0.0
281
+     */
282
+    public function searchValues(string $key, bool $lazy = false, ?int $typedAs = null): array {
283
+        $this->assertParams('', $key, true);
284
+        $this->loadConfig(null, $lazy);
285
+
286
+        /** @var array<array-key, array<array-key, mixed>> $cache */
287
+        if ($lazy) {
288
+            $cache = $this->lazyCache;
289
+        } else {
290
+            $cache = $this->fastCache;
291
+        }
292
+
293
+        $values = [];
294
+        foreach (array_keys($cache) as $app) {
295
+            if (isset($cache[$app][$key])) {
296
+                $values[$app] = $this->convertTypedValue($cache[$app][$key], $typedAs ?? $this->getValueType((string)$app, $key, $lazy));
297
+            }
298
+        }
299
+
300
+        return $values;
301
+    }
302
+
303
+
304
+    /**
305
+     * Get the config value as string.
306
+     * If the value does not exist the given default will be returned.
307
+     *
308
+     * Set lazy to `null` to ignore it and get the value from either source.
309
+     *
310
+     * **WARNING:** Method is internal and **SHOULD** not be used, as it is better to get the value with a type.
311
+     *
312
+     * @param string $app id of the app
313
+     * @param string $key config key
314
+     * @param string $default config value
315
+     * @param null|bool $lazy get config as lazy loaded or not. can be NULL
316
+     *
317
+     * @return string the value or $default
318
+     * @internal
319
+     * @since 29.0.0
320
+     * @see IAppConfig for explanation about lazy loading
321
+     * @see getValueString()
322
+     * @see getValueInt()
323
+     * @see getValueFloat()
324
+     * @see getValueBool()
325
+     * @see getValueArray()
326
+     */
327
+    public function getValueMixed(
328
+        string $app,
329
+        string $key,
330
+        string $default = '',
331
+        ?bool $lazy = false,
332
+    ): string {
333
+        try {
334
+            $lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy;
335
+        } catch (AppConfigUnknownKeyException) {
336
+            return $default;
337
+        }
338
+
339
+        return $this->getTypedValue(
340
+            $app,
341
+            $key,
342
+            $default,
343
+            $lazy,
344
+            self::VALUE_MIXED
345
+        );
346
+    }
347
+
348
+    /**
349
+     * @inheritDoc
350
+     *
351
+     * @param string $app id of the app
352
+     * @param string $key config key
353
+     * @param string $default default value
354
+     * @param bool $lazy search within lazy loaded config
355
+     *
356
+     * @return string stored config value or $default if not set in database
357
+     * @throws InvalidArgumentException if one of the argument format is invalid
358
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
359
+     * @since 29.0.0
360
+     * @see IAppConfig for explanation about lazy loading
361
+     */
362
+    public function getValueString(
363
+        string $app,
364
+        string $key,
365
+        string $default = '',
366
+        bool $lazy = false,
367
+    ): string {
368
+        return $this->getTypedValue($app, $key, $default, $lazy, self::VALUE_STRING);
369
+    }
370
+
371
+    /**
372
+     * @inheritDoc
373
+     *
374
+     * @param string $app id of the app
375
+     * @param string $key config key
376
+     * @param int $default default value
377
+     * @param bool $lazy search within lazy loaded config
378
+     *
379
+     * @return int stored config value or $default if not set in database
380
+     * @throws InvalidArgumentException if one of the argument format is invalid
381
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
382
+     * @since 29.0.0
383
+     * @see IAppConfig for explanation about lazy loading
384
+     */
385
+    public function getValueInt(
386
+        string $app,
387
+        string $key,
388
+        int $default = 0,
389
+        bool $lazy = false,
390
+    ): int {
391
+        return (int)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_INT);
392
+    }
393
+
394
+    /**
395
+     * @inheritDoc
396
+     *
397
+     * @param string $app id of the app
398
+     * @param string $key config key
399
+     * @param float $default default value
400
+     * @param bool $lazy search within lazy loaded config
401
+     *
402
+     * @return float stored config value or $default if not set in database
403
+     * @throws InvalidArgumentException if one of the argument format is invalid
404
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
405
+     * @since 29.0.0
406
+     * @see IAppConfig for explanation about lazy loading
407
+     */
408
+    public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float {
409
+        return (float)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_FLOAT);
410
+    }
411
+
412
+    /**
413
+     * @inheritDoc
414
+     *
415
+     * @param string $app id of the app
416
+     * @param string $key config key
417
+     * @param bool $default default value
418
+     * @param bool $lazy search within lazy loaded config
419
+     *
420
+     * @return bool stored config value or $default if not set in database
421
+     * @throws InvalidArgumentException if one of the argument format is invalid
422
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
423
+     * @since 29.0.0
424
+     * @see IAppConfig for explanation about lazy loading
425
+     */
426
+    public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool {
427
+        $b = strtolower($this->getTypedValue($app, $key, $default ? 'true' : 'false', $lazy, self::VALUE_BOOL));
428
+        return in_array($b, ['1', 'true', 'yes', 'on']);
429
+    }
430
+
431
+    /**
432
+     * @inheritDoc
433
+     *
434
+     * @param string $app id of the app
435
+     * @param string $key config key
436
+     * @param array $default default value
437
+     * @param bool $lazy search within lazy loaded config
438
+     *
439
+     * @return array stored config value or $default if not set in database
440
+     * @throws InvalidArgumentException if one of the argument format is invalid
441
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
442
+     * @since 29.0.0
443
+     * @see IAppConfig for explanation about lazy loading
444
+     */
445
+    public function getValueArray(
446
+        string $app,
447
+        string $key,
448
+        array $default = [],
449
+        bool $lazy = false,
450
+    ): array {
451
+        try {
452
+            $defaultJson = json_encode($default, JSON_THROW_ON_ERROR);
453
+            $value = json_decode($this->getTypedValue($app, $key, $defaultJson, $lazy, self::VALUE_ARRAY), true, flags: JSON_THROW_ON_ERROR);
454
+
455
+            return is_array($value) ? $value : [];
456
+        } catch (JsonException) {
457
+            return [];
458
+        }
459
+    }
460
+
461
+    /**
462
+     * @param string $app id of the app
463
+     * @param string $key config key
464
+     * @param string $default default value
465
+     * @param bool $lazy search within lazy loaded config
466
+     * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT}{@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
467
+     *
468
+     * @return string
469
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
470
+     * @throws InvalidArgumentException
471
+     */
472
+    private function getTypedValue(
473
+        string $app,
474
+        string $key,
475
+        string $default,
476
+        bool $lazy,
477
+        int $type,
478
+    ): string {
479
+        $this->assertParams($app, $key, valueType: $type);
480
+        $origKey = $key;
481
+        $matched = $this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default);
482
+        if ($default === null) {
483
+            // there is no logical reason for it to be null
484
+            throw new \Exception('default cannot be null');
485
+        }
486
+
487
+        // returns default if strictness of lexicon is set to WARNING (block and report)
488
+        if (!$matched) {
489
+            return $default;
490
+        }
491
+
492
+        $this->loadConfig($app, $lazy ?? true);
493
+
494
+        /**
495
+         * We ignore check if mixed type is requested.
496
+         * If type of stored value is set as mixed, we don't filter.
497
+         * If type of stored value is defined, we compare with the one requested.
498
+         */
499
+        $knownType = $this->valueTypes[$app][$key] ?? 0;
500
+        if (!$this->isTyped(self::VALUE_MIXED, $type)
501
+            && $knownType > 0
502
+            && !$this->isTyped(self::VALUE_MIXED, $knownType)
503
+            && !$this->isTyped($type, $knownType)) {
504
+            $this->logger->warning('conflict with value type from database', ['app' => $app, 'key' => $key, 'type' => $type, 'knownType' => $knownType]);
505
+            throw new AppConfigTypeConflictException('conflict with value type from database');
506
+        }
507
+
508
+        /**
509
+         * - the pair $app/$key cannot exist in both array,
510
+         * - we should still return an existing non-lazy value even if current method
511
+         *   is called with $lazy is true
512
+         *
513
+         * This way, lazyCache will be empty until the load for lazy config value is requested.
514
+         */
515
+        if (isset($this->lazyCache[$app][$key])) {
516
+            $value = $this->lazyCache[$app][$key];
517
+        } elseif (isset($this->fastCache[$app][$key])) {
518
+            $value = $this->fastCache[$app][$key];
519
+        } else {
520
+            return $default;
521
+        }
522
+
523
+        $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $knownType);
524
+        if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
525
+            // Only decrypt values that are stored encrypted
526
+            $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
527
+        }
528
+
529
+        // in case the key was modified while running matchAndApplyLexiconDefinition() we are
530
+        // interested to check options in case a modification of the value is needed
531
+        // ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN
532
+        if ($origKey !== $key && $type === self::VALUE_BOOL) {
533
+            $value = ($this->configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0';
534
+        }
535
+
536
+        return $value;
537
+    }
538
+
539
+    /**
540
+     * @inheritDoc
541
+     *
542
+     * @param string $app id of the app
543
+     * @param string $key config key
544
+     *
545
+     * @return int type of the value
546
+     * @throws AppConfigUnknownKeyException if config key is not known
547
+     * @since 29.0.0
548
+     * @see VALUE_STRING
549
+     * @see VALUE_INT
550
+     * @see VALUE_FLOAT
551
+     * @see VALUE_BOOL
552
+     * @see VALUE_ARRAY
553
+     */
554
+    public function getValueType(string $app, string $key, ?bool $lazy = null): int {
555
+        $type = self::VALUE_MIXED;
556
+        $ignorable = $lazy ?? false;
557
+        $this->matchAndApplyLexiconDefinition($app, $key, $ignorable, $type);
558
+        if ($type !== self::VALUE_MIXED) {
559
+            // a modified $type means config key is set in Lexicon
560
+            return $type;
561
+        }
562
+
563
+        $this->assertParams($app, $key);
564
+        $this->loadConfig($app, $lazy ?? true);
565
+
566
+        if (!isset($this->valueTypes[$app][$key])) {
567
+            throw new AppConfigUnknownKeyException('unknown config key');
568
+        }
569
+
570
+        $type = $this->valueTypes[$app][$key];
571
+        $type &= ~self::VALUE_SENSITIVE;
572
+        return $type;
573
+    }
574
+
575
+
576
+    /**
577
+     * Store a config key and its value in database as VALUE_MIXED
578
+     *
579
+     * **WARNING:** Method is internal and **MUST** not be used as it is best to set a real value type
580
+     *
581
+     * @param string $app id of the app
582
+     * @param string $key config key
583
+     * @param string $value config value
584
+     * @param bool $lazy set config as lazy loaded
585
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
586
+     *
587
+     * @return bool TRUE if value was different, therefor updated in database
588
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED
589
+     * @internal
590
+     * @since 29.0.0
591
+     * @see IAppConfig for explanation about lazy loading
592
+     * @see setValueString()
593
+     * @see setValueInt()
594
+     * @see setValueFloat()
595
+     * @see setValueBool()
596
+     * @see setValueArray()
597
+     */
598
+    public function setValueMixed(
599
+        string $app,
600
+        string $key,
601
+        string $value,
602
+        bool $lazy = false,
603
+        bool $sensitive = false,
604
+    ): bool {
605
+        return $this->setTypedValue(
606
+            $app,
607
+            $key,
608
+            $value,
609
+            $lazy,
610
+            self::VALUE_MIXED | ($sensitive ? self::VALUE_SENSITIVE : 0)
611
+        );
612
+    }
613
+
614
+
615
+    /**
616
+     * @inheritDoc
617
+     *
618
+     * @param string $app id of the app
619
+     * @param string $key config key
620
+     * @param string $value config value
621
+     * @param bool $lazy set config as lazy loaded
622
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
623
+     *
624
+     * @return bool TRUE if value was different, therefor updated in database
625
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
626
+     * @since 29.0.0
627
+     * @see IAppConfig for explanation about lazy loading
628
+     */
629
+    public function setValueString(
630
+        string $app,
631
+        string $key,
632
+        string $value,
633
+        bool $lazy = false,
634
+        bool $sensitive = false,
635
+    ): bool {
636
+        return $this->setTypedValue(
637
+            $app,
638
+            $key,
639
+            $value,
640
+            $lazy,
641
+            self::VALUE_STRING | ($sensitive ? self::VALUE_SENSITIVE : 0)
642
+        );
643
+    }
644
+
645
+    /**
646
+     * @inheritDoc
647
+     *
648
+     * @param string $app id of the app
649
+     * @param string $key config key
650
+     * @param int $value config value
651
+     * @param bool $lazy set config as lazy loaded
652
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
653
+     *
654
+     * @return bool TRUE if value was different, therefor updated in database
655
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
656
+     * @since 29.0.0
657
+     * @see IAppConfig for explanation about lazy loading
658
+     */
659
+    public function setValueInt(
660
+        string $app,
661
+        string $key,
662
+        int $value,
663
+        bool $lazy = false,
664
+        bool $sensitive = false,
665
+    ): bool {
666
+        if ($value > 2000000000) {
667
+            $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.');
668
+        }
669
+
670
+        return $this->setTypedValue(
671
+            $app,
672
+            $key,
673
+            (string)$value,
674
+            $lazy,
675
+            self::VALUE_INT | ($sensitive ? self::VALUE_SENSITIVE : 0)
676
+        );
677
+    }
678
+
679
+    /**
680
+     * @inheritDoc
681
+     *
682
+     * @param string $app id of the app
683
+     * @param string $key config key
684
+     * @param float $value config value
685
+     * @param bool $lazy set config as lazy loaded
686
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
687
+     *
688
+     * @return bool TRUE if value was different, therefor updated in database
689
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
690
+     * @since 29.0.0
691
+     * @see IAppConfig for explanation about lazy loading
692
+     */
693
+    public function setValueFloat(
694
+        string $app,
695
+        string $key,
696
+        float $value,
697
+        bool $lazy = false,
698
+        bool $sensitive = false,
699
+    ): bool {
700
+        return $this->setTypedValue(
701
+            $app,
702
+            $key,
703
+            (string)$value,
704
+            $lazy,
705
+            self::VALUE_FLOAT | ($sensitive ? self::VALUE_SENSITIVE : 0)
706
+        );
707
+    }
708
+
709
+    /**
710
+     * @inheritDoc
711
+     *
712
+     * @param string $app id of the app
713
+     * @param string $key config key
714
+     * @param bool $value config value
715
+     * @param bool $lazy set config as lazy loaded
716
+     *
717
+     * @return bool TRUE if value was different, therefor updated in database
718
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
719
+     * @since 29.0.0
720
+     * @see IAppConfig for explanation about lazy loading
721
+     */
722
+    public function setValueBool(
723
+        string $app,
724
+        string $key,
725
+        bool $value,
726
+        bool $lazy = false,
727
+    ): bool {
728
+        return $this->setTypedValue(
729
+            $app,
730
+            $key,
731
+            ($value) ? '1' : '0',
732
+            $lazy,
733
+            self::VALUE_BOOL
734
+        );
735
+    }
736
+
737
+    /**
738
+     * @inheritDoc
739
+     *
740
+     * @param string $app id of the app
741
+     * @param string $key config key
742
+     * @param array $value config value
743
+     * @param bool $lazy set config as lazy loaded
744
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
745
+     *
746
+     * @return bool TRUE if value was different, therefor updated in database
747
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
748
+     * @throws JsonException
749
+     * @since 29.0.0
750
+     * @see IAppConfig for explanation about lazy loading
751
+     */
752
+    public function setValueArray(
753
+        string $app,
754
+        string $key,
755
+        array $value,
756
+        bool $lazy = false,
757
+        bool $sensitive = false,
758
+    ): bool {
759
+        try {
760
+            return $this->setTypedValue(
761
+                $app,
762
+                $key,
763
+                json_encode($value, JSON_THROW_ON_ERROR),
764
+                $lazy,
765
+                self::VALUE_ARRAY | ($sensitive ? self::VALUE_SENSITIVE : 0)
766
+            );
767
+        } catch (JsonException $e) {
768
+            $this->logger->warning('could not setValueArray', ['app' => $app, 'key' => $key, 'exception' => $e]);
769
+            throw $e;
770
+        }
771
+    }
772
+
773
+    /**
774
+     * Store a config key and its value in database
775
+     *
776
+     * If config key is already known with the exact same config value and same sensitive/lazy status, the
777
+     * database is not updated. If config value was previously stored as sensitive, status will not be
778
+     * altered.
779
+     *
780
+     * @param string $app id of the app
781
+     * @param string $key config key
782
+     * @param string $value config value
783
+     * @param bool $lazy config set as lazy loaded
784
+     * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
785
+     *
786
+     * @return bool TRUE if value was updated in database
787
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
788
+     * @see IAppConfig for explanation about lazy loading
789
+     */
790
+    private function setTypedValue(
791
+        string $app,
792
+        string $key,
793
+        string $value,
794
+        bool $lazy,
795
+        int $type,
796
+    ): bool {
797
+        $this->assertParams($app, $key);
798
+        if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type)) {
799
+            return false; // returns false as database is not updated
800
+        }
801
+        $this->loadConfig(null, $lazy ?? true);
802
+
803
+        $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
804
+        $inserted = $refreshCache = false;
805
+
806
+        $origValue = $value;
807
+        if ($sensitive || ($this->hasKey($app, $key, $lazy) && $this->isSensitive($app, $key, $lazy))) {
808
+            $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
809
+        }
810
+
811
+        if ($this->hasKey($app, $key, $lazy)) {
812
+            /**
813
+             * no update if key is already known with set lazy status and value is
814
+             * not different, unless sensitivity is switched from false to true.
815
+             */
816
+            if ($origValue === $this->getTypedValue($app, $key, $value, $lazy ?? true, $type)
817
+                && (!$sensitive || $this->isSensitive($app, $key, $lazy))) {
818
+                return false;
819
+            }
820
+        } else {
821
+            /**
822
+             * if key is not known yet, we try to insert.
823
+             * It might fail if the key exists with a different lazy flag.
824
+             */
825
+            try {
826
+                $insert = $this->connection->getQueryBuilder();
827
+                $insert->insert('appconfig')
828
+                    ->setValue('appid', $insert->createNamedParameter($app))
829
+                    ->setValue('lazy', $insert->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
830
+                    ->setValue('type', $insert->createNamedParameter($type, IQueryBuilder::PARAM_INT))
831
+                    ->setValue('configkey', $insert->createNamedParameter($key))
832
+                    ->setValue('configvalue', $insert->createNamedParameter($value));
833
+                $insert->executeStatement();
834
+                $inserted = true;
835
+            } catch (DBException $e) {
836
+                if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
837
+                    throw $e; // TODO: throw exception or just log and returns false !?
838
+                }
839
+            }
840
+        }
841
+
842
+        /**
843
+         * We cannot insert a new row, meaning we need to update an already existing one
844
+         */
845
+        if (!$inserted) {
846
+            $currType = $this->valueTypes[$app][$key] ?? 0;
847
+            if ($currType === 0) { // this might happen when switching lazy loading status
848
+                $this->loadConfig(lazy: true);
849
+                $currType = $this->valueTypes[$app][$key] ?? 0;
850
+            }
851
+
852
+            /**
853
+             * This should only happen during the upgrade process from 28 to 29.
854
+             * We only log a warning and set it to VALUE_MIXED.
855
+             */
856
+            if ($currType === 0) {
857
+                $this->logger->warning('Value type is set to zero (0) in database. This is fine only during the upgrade process from 28 to 29.', ['app' => $app, 'key' => $key]);
858
+                $currType = self::VALUE_MIXED;
859
+            }
860
+
861
+            /**
862
+             * we only accept a different type from the one stored in database
863
+             * if the one stored in database is not-defined (VALUE_MIXED)
864
+             */
865
+            if (!$this->isTyped(self::VALUE_MIXED, $currType)
866
+                && ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) {
867
+                try {
868
+                    $currType = $this->convertTypeToString($currType);
869
+                    $type = $this->convertTypeToString($type);
870
+                } catch (AppConfigIncorrectTypeException) {
871
+                    // can be ignored, this was just needed for a better exception message.
872
+                }
873
+                throw new AppConfigTypeConflictException('conflict between new type (' . $type . ') and old type (' . $currType . ')');
874
+            }
875
+
876
+            // we fix $type if the stored value, or the new value as it might be changed, is set as sensitive
877
+            if ($sensitive || $this->isTyped(self::VALUE_SENSITIVE, $currType)) {
878
+                $type |= self::VALUE_SENSITIVE;
879
+            }
880
+
881
+            try {
882
+                if ($lazy !== $this->isLazy($app, $key)) {
883
+                    $refreshCache = true;
884
+                }
885
+            } catch (AppConfigUnknownKeyException) {
886
+                // pass
887
+            }
888
+
889
+            $update = $this->connection->getQueryBuilder();
890
+            $update->update('appconfig')
891
+                ->set('configvalue', $update->createNamedParameter($value))
892
+                ->set('lazy', $update->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
893
+                ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
894
+                ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
895
+                ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
896
+
897
+            $update->executeStatement();
898
+        }
899
+
900
+        if ($refreshCache) {
901
+            $this->clearCache();
902
+            return true;
903
+        }
904
+
905
+        // update local cache
906
+        if ($lazy) {
907
+            $this->lazyCache[$app][$key] = $value;
908
+        } else {
909
+            $this->fastCache[$app][$key] = $value;
910
+        }
911
+        $this->valueTypes[$app][$key] = $type;
912
+        $this->clearLocalCache();
913
+
914
+        return true;
915
+    }
916
+
917
+    /**
918
+     * Change the type of config value.
919
+     *
920
+     * **WARNING:** Method is internal and **MUST** not be used as it may break things.
921
+     *
922
+     * @param string $app id of the app
923
+     * @param string $key config key
924
+     * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
925
+     *
926
+     * @return bool TRUE if database update were necessary
927
+     * @throws AppConfigUnknownKeyException if $key is now known in database
928
+     * @throws AppConfigIncorrectTypeException if $type is not valid
929
+     * @internal
930
+     * @since 29.0.0
931
+     */
932
+    public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool {
933
+        $this->assertParams($app, $key);
934
+        $this->loadConfig(lazy: true);
935
+        $this->matchAndApplyLexiconDefinition($app, $key);
936
+        $this->isLazy($app, $key); // confirm key exists
937
+
938
+        // type can only be one type
939
+        if (!in_array($type, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
940
+            throw new AppConfigIncorrectTypeException('Unknown value type');
941
+        }
942
+
943
+        $currType = $this->valueTypes[$app][$key];
944
+        if (($type | self::VALUE_SENSITIVE) === ($currType | self::VALUE_SENSITIVE)) {
945
+            return false;
946
+        }
947
+
948
+        // we complete with sensitive flag if the stored value is set as sensitive
949
+        if ($this->isTyped(self::VALUE_SENSITIVE, $currType)) {
950
+            $type = $type | self::VALUE_SENSITIVE;
951
+        }
952
+
953
+        $update = $this->connection->getQueryBuilder();
954
+        $update->update('appconfig')
955
+            ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
956
+            ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
957
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
958
+        $update->executeStatement();
959
+        $this->valueTypes[$app][$key] = $type;
960
+
961
+        return true;
962
+    }
963
+
964
+
965
+    /**
966
+     * @inheritDoc
967
+     *
968
+     * @param string $app id of the app
969
+     * @param string $key config key
970
+     * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
971
+     *
972
+     * @return bool TRUE if entry was found in database and an update was necessary
973
+     * @since 29.0.0
974
+     */
975
+    public function updateSensitive(string $app, string $key, bool $sensitive): bool {
976
+        $this->assertParams($app, $key);
977
+        $this->loadConfig(lazy: true);
978
+        $this->matchAndApplyLexiconDefinition($app, $key);
979
+
980
+        try {
981
+            if ($sensitive === $this->isSensitive($app, $key, null)) {
982
+                return false;
983
+            }
984
+        } catch (AppConfigUnknownKeyException $e) {
985
+            return false;
986
+        }
987
+
988
+        $lazy = $this->isLazy($app, $key);
989
+        if ($lazy) {
990
+            $cache = $this->lazyCache;
991
+        } else {
992
+            $cache = $this->fastCache;
993
+        }
994
+
995
+        if (!isset($cache[$app][$key])) {
996
+            throw new AppConfigUnknownKeyException('unknown config key');
997
+        }
998
+
999
+        /**
1000
+         * type returned by getValueType() is already cleaned from sensitive flag
1001
+         * we just need to update it based on $sensitive and store it in database
1002
+         */
1003
+        $type = $this->getValueType($app, $key);
1004
+        $value = $cache[$app][$key];
1005
+        if ($sensitive) {
1006
+            $type |= self::VALUE_SENSITIVE;
1007
+            $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
1008
+        } else {
1009
+            $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
1010
+        }
1011
+
1012
+        $update = $this->connection->getQueryBuilder();
1013
+        $update->update('appconfig')
1014
+            ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
1015
+            ->set('configvalue', $update->createNamedParameter($value))
1016
+            ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
1017
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1018
+        $update->executeStatement();
1019
+
1020
+        $this->valueTypes[$app][$key] = $type;
1021
+
1022
+        return true;
1023
+    }
1024
+
1025
+    /**
1026
+     * @inheritDoc
1027
+     *
1028
+     * @param string $app id of the app
1029
+     * @param string $key config key
1030
+     * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
1031
+     *
1032
+     * @return bool TRUE if entry was found in database and an update was necessary
1033
+     * @since 29.0.0
1034
+     */
1035
+    public function updateLazy(string $app, string $key, bool $lazy): bool {
1036
+        $this->assertParams($app, $key);
1037
+        $this->loadConfig(lazy: true);
1038
+        $this->matchAndApplyLexiconDefinition($app, $key);
1039
+
1040
+        try {
1041
+            if ($lazy === $this->isLazy($app, $key)) {
1042
+                return false;
1043
+            }
1044
+        } catch (AppConfigUnknownKeyException $e) {
1045
+            return false;
1046
+        }
1047
+
1048
+        $update = $this->connection->getQueryBuilder();
1049
+        $update->update('appconfig')
1050
+            ->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
1051
+            ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
1052
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1053
+        $update->executeStatement();
1054
+
1055
+        // At this point, it is a lot safer to clean cache
1056
+        $this->clearCache();
1057
+
1058
+        return true;
1059
+    }
1060
+
1061
+    /**
1062
+     * @inheritDoc
1063
+     *
1064
+     * @param string $app id of the app
1065
+     * @param string $key config key
1066
+     *
1067
+     * @return array
1068
+     * @throws AppConfigUnknownKeyException if config key is not known in database
1069
+     * @since 29.0.0
1070
+     */
1071
+    public function getDetails(string $app, string $key): array {
1072
+        $this->assertParams($app, $key);
1073
+        $this->loadConfig(lazy: true);
1074
+        $this->matchAndApplyLexiconDefinition($app, $key);
1075
+        $lazy = $this->isLazy($app, $key);
1076
+
1077
+        if ($lazy) {
1078
+            $cache = $this->lazyCache;
1079
+        } else {
1080
+            $cache = $this->fastCache;
1081
+        }
1082
+
1083
+        $type = $this->getValueType($app, $key);
1084
+        try {
1085
+            $typeString = $this->convertTypeToString($type);
1086
+        } catch (AppConfigIncorrectTypeException $e) {
1087
+            $this->logger->warning('type stored in database is not correct', ['exception' => $e, 'type' => $type]);
1088
+            $typeString = (string)$type;
1089
+        }
1090
+
1091
+        if (!isset($cache[$app][$key])) {
1092
+            throw new AppConfigUnknownKeyException('unknown config key');
1093
+        }
1094
+
1095
+        $value = $cache[$app][$key];
1096
+        $sensitive = $this->isSensitive($app, $key, null);
1097
+        if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
1098
+            $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
1099
+        }
1100
+
1101
+        return [
1102
+            'app' => $app,
1103
+            'key' => $key,
1104
+            'value' => $value,
1105
+            'type' => $type,
1106
+            'lazy' => $lazy,
1107
+            'typeString' => $typeString,
1108
+            'sensitive' => $sensitive
1109
+        ];
1110
+    }
1111
+
1112
+    /**
1113
+     * @inheritDoc
1114
+     *
1115
+     * @param string $app id of the app
1116
+     * @param string $key config key
1117
+     *
1118
+     * @return array{app: string, key: string, lazy?: bool, valueType?: ValueType, valueTypeName?: string, sensitive?: bool, internal?: bool, default?: string, definition?: string, note?: string}
1119
+     * @since 32.0.0
1120
+     */
1121
+    public function getKeyDetails(string $app, string $key): array {
1122
+        $this->assertParams($app, $key);
1123
+        try {
1124
+            $details = $this->getDetails($app, $key);
1125
+        } catch (AppConfigUnknownKeyException) {
1126
+            $details = [
1127
+                'app' => $app,
1128
+                'key' => $key
1129
+            ];
1130
+        }
1131
+
1132
+        /** @var Entry $lexiconEntry */
1133
+        try {
1134
+            $lazy = false;
1135
+            $this->matchAndApplyLexiconDefinition($app, $key, $lazy, lexiconEntry: $lexiconEntry);
1136
+        } catch (AppConfigTypeConflictException|AppConfigUnknownKeyException) {
1137
+            // can be ignored
1138
+        }
1139
+
1140
+        if ($lexiconEntry !== null) {
1141
+            $details = array_merge($details, [
1142
+                'lazy' => $lexiconEntry->isLazy(),
1143
+                'valueType' => $lexiconEntry->getValueType(),
1144
+                'valueTypeName' => $lexiconEntry->getValueType()->name,
1145
+                'sensitive' => $lexiconEntry->isFlagged(self::FLAG_SENSITIVE),
1146
+                'internal' => $lexiconEntry->isFlagged(self::FLAG_INTERNAL),
1147
+                'default' => $lexiconEntry->getDefault($this->presetManager->getLexiconPreset()),
1148
+                'definition' => $lexiconEntry->getDefinition(),
1149
+                'note' => $lexiconEntry->getNote(),
1150
+            ]);
1151
+        }
1152
+
1153
+        return array_filter($details, static fn ($v): bool => ($v !== null));
1154
+    }
1155
+
1156
+    /**
1157
+     * @param string $type
1158
+     *
1159
+     * @return int
1160
+     * @throws AppConfigIncorrectTypeException
1161
+     * @since 29.0.0
1162
+     */
1163
+    public function convertTypeToInt(string $type): int {
1164
+        return match (strtolower($type)) {
1165
+            'mixed' => IAppConfig::VALUE_MIXED,
1166
+            'string' => IAppConfig::VALUE_STRING,
1167
+            'integer' => IAppConfig::VALUE_INT,
1168
+            'float' => IAppConfig::VALUE_FLOAT,
1169
+            'boolean' => IAppConfig::VALUE_BOOL,
1170
+            'array' => IAppConfig::VALUE_ARRAY,
1171
+            default => throw new AppConfigIncorrectTypeException('Unknown type ' . $type)
1172
+        };
1173
+    }
1174
+
1175
+    /**
1176
+     * @param int $type
1177
+     *
1178
+     * @return string
1179
+     * @throws AppConfigIncorrectTypeException
1180
+     * @since 29.0.0
1181
+     */
1182
+    public function convertTypeToString(int $type): string {
1183
+        $type &= ~self::VALUE_SENSITIVE;
1184
+
1185
+        return match ($type) {
1186
+            IAppConfig::VALUE_MIXED => 'mixed',
1187
+            IAppConfig::VALUE_STRING => 'string',
1188
+            IAppConfig::VALUE_INT => 'integer',
1189
+            IAppConfig::VALUE_FLOAT => 'float',
1190
+            IAppConfig::VALUE_BOOL => 'boolean',
1191
+            IAppConfig::VALUE_ARRAY => 'array',
1192
+            default => throw new AppConfigIncorrectTypeException('Unknown numeric type ' . $type)
1193
+        };
1194
+    }
1195
+
1196
+    /**
1197
+     * @inheritDoc
1198
+     *
1199
+     * @param string $app id of the app
1200
+     * @param string $key config key
1201
+     *
1202
+     * @since 29.0.0
1203
+     */
1204
+    public function deleteKey(string $app, string $key): void {
1205
+        $this->assertParams($app, $key);
1206
+        $this->matchAndApplyLexiconDefinition($app, $key);
1207
+
1208
+        $qb = $this->connection->getQueryBuilder();
1209
+        $qb->delete('appconfig')
1210
+            ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
1211
+            ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
1212
+        $qb->executeStatement();
1213
+
1214
+        unset($this->lazyCache[$app][$key]);
1215
+        unset($this->fastCache[$app][$key]);
1216
+        unset($this->valueTypes[$app][$key]);
1217
+        $this->clearLocalCache();
1218
+    }
1219
+
1220
+    /**
1221
+     * @inheritDoc
1222
+     *
1223
+     * @param string $app id of the app
1224
+     *
1225
+     * @since 29.0.0
1226
+     */
1227
+    public function deleteApp(string $app): void {
1228
+        $this->assertParams($app);
1229
+        $qb = $this->connection->getQueryBuilder();
1230
+        $qb->delete('appconfig')
1231
+            ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
1232
+        $qb->executeStatement();
1233
+
1234
+        $this->clearCache();
1235
+    }
1236
+
1237
+    /**
1238
+     * @inheritDoc
1239
+     *
1240
+     * @param bool $reload set to TRUE to refill cache instantly after clearing it
1241
+     *
1242
+     * @internal
1243
+     * @since 29.0.0
1244
+     */
1245
+    public function clearCache(bool $reload = false): void {
1246
+        $this->lazyLoaded = $this->fastLoaded = false;
1247
+        $this->lazyCache = $this->fastCache = $this->valueTypes = $this->configLexiconDetails = [];
1248
+        $this->localCache?->remove(self::LOCAL_CACHE_KEY);
1249
+
1250
+        if (!$reload) {
1251
+            return;
1252
+        }
1253
+
1254
+        $this->loadConfig(lazy: true);
1255
+    }
1256
+
1257
+
1258
+    /**
1259
+     * For debug purpose.
1260
+     * Returns the cached data.
1261
+     *
1262
+     * @return array
1263
+     * @since 29.0.0
1264
+     * @internal
1265
+     */
1266
+    public function statusCache(): array {
1267
+        return [
1268
+            'fastLoaded' => $this->fastLoaded,
1269
+            'fastCache' => $this->fastCache,
1270
+            'lazyLoaded' => $this->lazyLoaded,
1271
+            'lazyCache' => $this->lazyCache,
1272
+        ];
1273
+    }
1274
+
1275
+    /**
1276
+     * @param int $needle bitflag to search
1277
+     * @param int $type known value
1278
+     *
1279
+     * @return bool TRUE if bitflag $needle is set in $type
1280
+     */
1281
+    private function isTyped(int $needle, int $type): bool {
1282
+        return (($needle & $type) !== 0);
1283
+    }
1284
+
1285
+    /**
1286
+     * Confirm the string set for app and key fit the database description
1287
+     *
1288
+     * @param string $app assert $app fit in database
1289
+     * @param string $configKey assert config key fit in database
1290
+     * @param bool $allowEmptyApp $app can be empty string
1291
+     * @param int $valueType assert value type is only one type
1292
+     *
1293
+     * @throws InvalidArgumentException
1294
+     */
1295
+    private function assertParams(string $app = '', string $configKey = '', bool $allowEmptyApp = false, int $valueType = -1): void {
1296
+        if (!$allowEmptyApp && $app === '') {
1297
+            throw new InvalidArgumentException('app cannot be an empty string');
1298
+        }
1299
+        if (strlen($app) > self::APP_MAX_LENGTH) {
1300
+            throw new InvalidArgumentException(
1301
+                'Value (' . $app . ') for app is too long (' . self::APP_MAX_LENGTH . ')'
1302
+            );
1303
+        }
1304
+        if (strlen($configKey) > self::KEY_MAX_LENGTH) {
1305
+            throw new InvalidArgumentException('Value (' . $configKey . ') for key is too long (' . self::KEY_MAX_LENGTH . ')');
1306
+        }
1307
+        if ($valueType > -1) {
1308
+            $valueType &= ~self::VALUE_SENSITIVE;
1309
+            if (!in_array($valueType, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
1310
+                throw new InvalidArgumentException('Unknown value type');
1311
+            }
1312
+        }
1313
+    }
1314
+
1315
+    /**
1316
+     * Load normal config or config set as lazy loaded
1317
+     *
1318
+     * @param bool $lazy set to TRUE to also load config values set as lazy loaded
1319
+     */
1320
+    private function loadConfig(?string $app = null, bool $lazy = false): void {
1321
+        if ($this->isLoaded($lazy)) {
1322
+            return;
1323
+        }
1324
+
1325
+        // if lazy is null or true, we debug log
1326
+        if ($lazy === true && $app !== null) {
1327
+            $exception = new \RuntimeException('The loading of lazy AppConfig values have been triggered by app "' . $app . '"');
1328
+            $this->logger->debug($exception->getMessage(), ['exception' => $exception, 'app' => $app]);
1329
+        }
1330
+
1331
+        $loadLazyOnly = $lazy && $this->isLoaded();
1332
+
1333
+        /** @var array<mixed> */
1334
+        $cacheContent = $this->localCache?->get(self::LOCAL_CACHE_KEY) ?? [];
1335
+        $includesLazyValues = !empty($cacheContent) && !empty($cacheContent['lazyCache']);
1336
+        if (!empty($cacheContent) && (!$lazy || $includesLazyValues)) {
1337
+            $this->valueTypes = $cacheContent['valueTypes'];
1338
+            $this->fastCache = $cacheContent['fastCache'];
1339
+            $this->fastLoaded = !empty($this->fastCache);
1340
+            if ($includesLazyValues) {
1341
+                $this->lazyCache = $cacheContent['lazyCache'];
1342
+                $this->lazyLoaded = !empty($this->lazyCache);
1343
+            }
1344
+            return;
1345
+        }
1346
+
1347
+        // Otherwise no cache available and we need to fetch from database
1348
+        $qb = $this->connection->getQueryBuilder();
1349
+        $qb->from('appconfig')
1350
+            ->select('appid', 'configkey', 'configvalue', 'type');
1351
+
1352
+        if ($lazy === false) {
1353
+            $qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
1354
+        } else {
1355
+            if ($loadLazyOnly) {
1356
+                $qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)));
1357
+            }
1358
+            $qb->addSelect('lazy');
1359
+        }
1360
+
1361
+        $result = $qb->executeQuery();
1362
+        $rows = $result->fetchAll();
1363
+        foreach ($rows as $row) {
1364
+            // most of the time, 'lazy' is not in the select because its value is already known
1365
+            if ($lazy && ((int)$row['lazy']) === 1) {
1366
+                $this->lazyCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1367
+            } else {
1368
+                $this->fastCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1369
+            }
1370
+            $this->valueTypes[$row['appid']][$row['configkey']] = (int)($row['type'] ?? 0);
1371
+        }
1372
+
1373
+        $result->closeCursor();
1374
+        $this->localCache?->set(
1375
+            self::LOCAL_CACHE_KEY,
1376
+            [
1377
+                'fastCache' => $this->fastCache,
1378
+                'lazyCache' => $this->lazyCache,
1379
+                'valueTypes' => $this->valueTypes,
1380
+            ],
1381
+            self::LOCAL_CACHE_TTL,
1382
+        );
1383
+
1384
+        $this->fastLoaded = true;
1385
+        $this->lazyLoaded = $lazy;
1386
+    }
1387
+
1388
+    /**
1389
+     * @param bool $lazy - If set to true then also check if lazy values are loaded
1390
+     */
1391
+    private function isLoaded(bool $lazy = false): bool {
1392
+        return $this->fastLoaded && (!$lazy || $this->lazyLoaded);
1393
+    }
1394
+
1395
+    /**
1396
+     * Gets the config value
1397
+     *
1398
+     * @param string $app app
1399
+     * @param string $key key
1400
+     * @param string $default - Default value if the key does not exist
1401
+     *
1402
+     * @return string the value or $default
1403
+     * @deprecated 29.0.0 use getValue*()
1404
+     *
1405
+     * This function gets a value from the appconfig table. If the key does
1406
+     * not exist the default value will be returned
1407
+     */
1408
+    public function getValue($app, $key, $default = '') {
1409
+        $this->loadConfig($app);
1410
+        $this->matchAndApplyLexiconDefinition($app, $key);
1411
+
1412
+        return $this->fastCache[$app][$key] ?? $default;
1413
+    }
1414
+
1415
+    /**
1416
+     * Sets a value. If the key did not exist before it will be created.
1417
+     *
1418
+     * @param string $app app
1419
+     * @param string $key key
1420
+     * @param string|float|int $value value
1421
+     *
1422
+     * @return bool True if the value was inserted or updated, false if the value was the same
1423
+     * @throws AppConfigTypeConflictException
1424
+     * @throws AppConfigUnknownKeyException
1425
+     * @deprecated 29.0.0
1426
+     */
1427
+    public function setValue($app, $key, $value) {
1428
+        /**
1429
+         * TODO: would it be overkill, or decently improve performance, to catch
1430
+         * call to this method with $key='enabled' and 'hide' config value related
1431
+         * to $app when the app is disabled (by modifying entry in database: lazy=lazy+2)
1432
+         * or enabled (lazy=lazy-2)
1433
+         *
1434
+         * this solution would remove the loading of config values from disabled app
1435
+         * unless calling the method.
1436
+         */
1437
+        return $this->setTypedValue($app, $key, (string)$value, false, self::VALUE_MIXED);
1438
+    }
1439
+
1440
+
1441
+    /**
1442
+     * get multiple values, either the app or key can be used as wildcard by setting it to false
1443
+     *
1444
+     * @param string|false $app
1445
+     * @param string|false $key
1446
+     *
1447
+     * @return array|false
1448
+     * @deprecated 29.0.0 use {@see getAllValues()}
1449
+     */
1450
+    public function getValues($app, $key) {
1451
+        if (($app !== false) === ($key !== false)) {
1452
+            return false;
1453
+        }
1454
+
1455
+        $key = ($key === false) ? '' : $key;
1456
+        if (!$app) {
1457
+            return $this->searchValues($key, false, self::VALUE_MIXED);
1458
+        } else {
1459
+            return $this->getAllValues($app, $key);
1460
+        }
1461
+    }
1462
+
1463
+    /**
1464
+     * get all values of the app or and filters out sensitive data
1465
+     *
1466
+     * @param string $app
1467
+     *
1468
+     * @return array
1469
+     * @deprecated 29.0.0 use {@see getAllValues()}
1470
+     */
1471
+    public function getFilteredValues($app) {
1472
+        return $this->getAllValues($app, filtered: true);
1473
+    }
1474
+
1475
+
1476
+    /**
1477
+     * **Warning:** avoid default NULL value for $lazy as this will
1478
+     * load all lazy values from the database
1479
+     *
1480
+     * @param string $app
1481
+     * @param array<string, string> $values ['key' => 'value']
1482
+     * @param bool|null $lazy
1483
+     *
1484
+     * @return array<string, string|int|float|bool|array>
1485
+     */
1486
+    private function formatAppValues(string $app, array $values, ?bool $lazy = null): array {
1487
+        foreach ($values as $key => $value) {
1488
+            try {
1489
+                $type = $this->getValueType($app, $key, $lazy);
1490
+            } catch (AppConfigUnknownKeyException) {
1491
+                continue;
1492
+            }
1493
+
1494
+            $values[$key] = $this->convertTypedValue($value, $type);
1495
+        }
1496
+
1497
+        return $values;
1498
+    }
1499
+
1500
+    /**
1501
+     * convert string value to the expected type
1502
+     *
1503
+     * @param string $value
1504
+     * @param int $type
1505
+     *
1506
+     * @return string|int|float|bool|array
1507
+     */
1508
+    private function convertTypedValue(string $value, int $type): string|int|float|bool|array {
1509
+        switch ($type) {
1510
+            case self::VALUE_INT:
1511
+                return (int)$value;
1512
+            case self::VALUE_FLOAT:
1513
+                return (float)$value;
1514
+            case self::VALUE_BOOL:
1515
+                return in_array(strtolower($value), ['1', 'true', 'yes', 'on']);
1516
+            case self::VALUE_ARRAY:
1517
+                try {
1518
+                    return json_decode($value, true, flags: JSON_THROW_ON_ERROR);
1519
+                } catch (JsonException $e) {
1520
+                    // ignoreable
1521
+                }
1522
+                break;
1523
+        }
1524
+        return $value;
1525
+    }
1526
+
1527
+    /**
1528
+     * @param string $app
1529
+     *
1530
+     * @return string[]
1531
+     * @deprecated 29.0.0 data sensitivity should be set when calling setValue*()
1532
+     */
1533
+    private function getSensitiveKeys(string $app): array {
1534
+        $sensitiveValues = [
1535
+            'circles' => [
1536
+                '/^key_pairs$/',
1537
+                '/^local_gskey$/',
1538
+            ],
1539
+            'call_summary_bot' => [
1540
+                '/^secret_(.*)$/',
1541
+            ],
1542
+            'external' => [
1543
+                '/^sites$/',
1544
+                '/^jwt_token_privkey_(.*)$/',
1545
+            ],
1546
+            'globalsiteselector' => [
1547
+                '/^gss\.jwt\.key$/',
1548
+            ],
1549
+            'gpgmailer' => [
1550
+                '/^GpgServerKey$/',
1551
+            ],
1552
+            'integration_discourse' => [
1553
+                '/^private_key$/',
1554
+                '/^public_key$/',
1555
+            ],
1556
+            'integration_dropbox' => [
1557
+                '/^client_id$/',
1558
+                '/^client_secret$/',
1559
+            ],
1560
+            'integration_github' => [
1561
+                '/^client_id$/',
1562
+                '/^client_secret$/',
1563
+            ],
1564
+            'integration_gitlab' => [
1565
+                '/^client_id$/',
1566
+                '/^client_secret$/',
1567
+                '/^oauth_instance_url$/',
1568
+            ],
1569
+            'integration_google' => [
1570
+                '/^client_id$/',
1571
+                '/^client_secret$/',
1572
+            ],
1573
+            'integration_jira' => [
1574
+                '/^client_id$/',
1575
+                '/^client_secret$/',
1576
+                '/^forced_instance_url$/',
1577
+            ],
1578
+            'integration_onedrive' => [
1579
+                '/^client_id$/',
1580
+                '/^client_secret$/',
1581
+            ],
1582
+            'integration_openproject' => [
1583
+                '/^client_id$/',
1584
+                '/^client_secret$/',
1585
+                '/^oauth_instance_url$/',
1586
+            ],
1587
+            'integration_reddit' => [
1588
+                '/^client_id$/',
1589
+                '/^client_secret$/',
1590
+            ],
1591
+            'integration_suitecrm' => [
1592
+                '/^client_id$/',
1593
+                '/^client_secret$/',
1594
+                '/^oauth_instance_url$/',
1595
+            ],
1596
+            'integration_twitter' => [
1597
+                '/^consumer_key$/',
1598
+                '/^consumer_secret$/',
1599
+                '/^followed_user$/',
1600
+            ],
1601
+            'integration_zammad' => [
1602
+                '/^client_id$/',
1603
+                '/^client_secret$/',
1604
+                '/^oauth_instance_url$/',
1605
+            ],
1606
+            'maps' => [
1607
+                '/^mapboxAPIKEY$/',
1608
+            ],
1609
+            'notify_push' => [
1610
+                '/^cookie$/',
1611
+            ],
1612
+            'onlyoffice' => [
1613
+                '/^jwt_secret$/',
1614
+            ],
1615
+            'passwords' => [
1616
+                '/^SSEv1ServerKey$/',
1617
+            ],
1618
+            'serverinfo' => [
1619
+                '/^token$/',
1620
+            ],
1621
+            'spreed' => [
1622
+                '/^bridge_bot_password$/',
1623
+                '/^hosted-signaling-server-(.*)$/',
1624
+                '/^recording_servers$/',
1625
+                '/^signaling_servers$/',
1626
+                '/^signaling_ticket_secret$/',
1627
+                '/^signaling_token_privkey_(.*)$/',
1628
+                '/^signaling_token_pubkey_(.*)$/',
1629
+                '/^sip_bridge_dialin_info$/',
1630
+                '/^sip_bridge_shared_secret$/',
1631
+                '/^stun_servers$/',
1632
+                '/^turn_servers$/',
1633
+                '/^turn_server_secret$/',
1634
+            ],
1635
+            'support' => [
1636
+                '/^last_response$/',
1637
+                '/^potential_subscription_key$/',
1638
+                '/^subscription_key$/',
1639
+            ],
1640
+            'theming' => [
1641
+                '/^imprintUrl$/',
1642
+                '/^privacyUrl$/',
1643
+                '/^slogan$/',
1644
+                '/^url$/',
1645
+            ],
1646
+            'twofactor_gateway' => [
1647
+                '/^.*token$/',
1648
+            ],
1649
+            'user_ldap' => [
1650
+                '/^(s..)?ldap_agent_password$/',
1651
+            ],
1652
+            'user_saml' => [
1653
+                '/^idp-x509cert$/',
1654
+            ],
1655
+            'whiteboard' => [
1656
+                '/^jwt_secret_key$/',
1657
+            ],
1658
+        ];
1659
+
1660
+        return $sensitiveValues[$app] ?? [];
1661
+    }
1662
+
1663
+    /**
1664
+     * Clear all the cached app config values
1665
+     * New cache will be generated next time a config value is retrieved
1666
+     *
1667
+     * @deprecated 29.0.0 use {@see clearCache()}
1668
+     */
1669
+    public function clearCachedConfig(): void {
1670
+        $this->clearCache();
1671
+    }
1672
+
1673
+    /**
1674
+     * Match and apply current use of config values with defined lexicon.
1675
+     * Set $lazy to NULL only if only interested into checking that $key is alias.
1676
+     *
1677
+     * @throws AppConfigUnknownKeyException
1678
+     * @throws AppConfigTypeConflictException
1679
+     * @return bool TRUE if everything is fine compared to lexicon or lexicon does not exist
1680
+     */
1681
+    private function matchAndApplyLexiconDefinition(
1682
+        string $app,
1683
+        string &$key,
1684
+        ?bool &$lazy = null,
1685
+        int &$type = self::VALUE_MIXED,
1686
+        ?string &$default = null,
1687
+        ?Entry &$lexiconEntry = null,
1688
+    ): bool {
1689
+        if (in_array($key,
1690
+            [
1691
+                'enabled',
1692
+                'installed_version',
1693
+                'types',
1694
+            ])) {
1695
+            return true; // we don't break stuff for this list of config keys.
1696
+        }
1697
+        $configDetails = $this->getConfigDetailsFromLexicon($app);
1698
+        if (array_key_exists($key, $configDetails['aliases']) && !$this->ignoreLexiconAliases) {
1699
+            // in case '$rename' is set in ConfigLexiconEntry, we use the new config key
1700
+            $key = $configDetails['aliases'][$key];
1701
+        }
1702
+
1703
+        if (!array_key_exists($key, $configDetails['entries'])) {
1704
+            return $this->applyLexiconStrictness($configDetails['strictness'], $app . '/' . $key);
1705
+        }
1706
+
1707
+        // if lazy is NULL, we ignore all check on the type/lazyness/default from Lexicon
1708
+        if ($lazy === null) {
1709
+            return true;
1710
+        }
1711
+
1712
+        /** @var Entry $lexiconEntry */
1713
+        $lexiconEntry = $configDetails['entries'][$key];
1714
+        $type &= ~self::VALUE_SENSITIVE;
1715
+
1716
+        $appConfigValueType = $lexiconEntry->getValueType()->toAppConfigFlag();
1717
+        if ($type === self::VALUE_MIXED) {
1718
+            $type = $appConfigValueType; // we overwrite if value was requested as mixed
1719
+        } elseif ($appConfigValueType !== $type) {
1720
+            throw new AppConfigTypeConflictException('The app config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
1721
+        }
1722
+
1723
+        $lazy = $lexiconEntry->isLazy();
1724
+        // only look for default if needed, default from Lexicon got priority
1725
+        if ($default !== null) {
1726
+            $default = $lexiconEntry->getDefault($this->presetManager->getLexiconPreset()) ?? $default;
1727
+        }
1728
+
1729
+        if ($lexiconEntry->isFlagged(self::FLAG_SENSITIVE)) {
1730
+            $type |= self::VALUE_SENSITIVE;
1731
+        }
1732
+        if ($lexiconEntry->isDeprecated()) {
1733
+            $this->logger->notice('App config key ' . $app . '/' . $key . ' is set as deprecated.');
1734
+        }
1735
+
1736
+        return true;
1737
+    }
1738
+
1739
+    /**
1740
+     * manage ConfigLexicon behavior based on strictness set in IConfigLexicon
1741
+     *
1742
+     * @param Strictness|null $strictness
1743
+     * @param string $line
1744
+     *
1745
+     * @return bool TRUE if conflict can be fully ignored, FALSE if action should be not performed
1746
+     * @throws AppConfigUnknownKeyException if strictness implies exception
1747
+     * @see \OCP\Config\Lexicon\ILexicon::getStrictness()
1748
+     */
1749
+    private function applyLexiconStrictness(?Strictness $strictness, string $configAppKey): bool {
1750
+        if ($strictness === null) {
1751
+            return true;
1752
+        }
1753
+
1754
+        $line = 'The app config key ' . $configAppKey . ' is not defined in the config lexicon';
1755
+        switch ($strictness) {
1756
+            case Strictness::IGNORE:
1757
+                return true;
1758
+            case Strictness::NOTICE:
1759
+                if (!in_array($configAppKey, $this->strictnessApplied, true)) {
1760
+                    $this->strictnessApplied[] = $configAppKey;
1761
+                    $this->logger->notice($line);
1762
+                }
1763
+                return true;
1764
+            case Strictness::WARNING:
1765
+                if (!in_array($configAppKey, $this->strictnessApplied, true)) {
1766
+                    $this->strictnessApplied[] = $configAppKey;
1767
+                    $this->logger->warning($line);
1768
+                }
1769
+                return false;
1770
+        }
1771
+
1772
+        throw new AppConfigUnknownKeyException($line);
1773
+    }
1774
+
1775
+    /**
1776
+     * extract details from registered $appId's config lexicon
1777
+     *
1778
+     * @param string $appId
1779
+     * @internal
1780
+     *
1781
+     * @return array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}
1782
+     */
1783
+    public function getConfigDetailsFromLexicon(string $appId): array {
1784
+        if (!array_key_exists($appId, $this->configLexiconDetails)) {
1785
+            $entries = $aliases = [];
1786
+            $bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
1787
+            $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId);
1788
+            foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) {
1789
+                $entries[$configEntry->getKey()] = $configEntry;
1790
+                $newName = $configEntry->getRename();
1791
+                if ($newName !== null) {
1792
+                    $aliases[$newName] = $configEntry->getKey();
1793
+                }
1794
+            }
1795
+
1796
+            $this->configLexiconDetails[$appId] = [
1797
+                'entries' => $entries,
1798
+                'aliases' => $aliases,
1799
+                'strictness' => $configLexicon?->getStrictness() ?? Strictness::IGNORE
1800
+            ];
1801
+        }
1802
+
1803
+        return $this->configLexiconDetails[$appId];
1804
+    }
1805
+
1806
+    /**
1807
+     * get Lexicon Entry using appId and config key entry
1808
+     *
1809
+     * @return Entry|null NULL if entry does not exist in app's Lexicon
1810
+     * @internal
1811
+     */
1812
+    public function getLexiconEntry(string $appId, string $key): ?Entry {
1813
+        return $this->getConfigDetailsFromLexicon($appId)['entries'][$key] ?? null;
1814
+    }
1815
+
1816
+    /**
1817
+     * if set to TRUE, ignore aliases defined in Config Lexicon during the use of the methods of this class
1818
+     *
1819
+     * @internal
1820
+     */
1821
+    public function ignoreLexiconAliases(bool $ignore): void {
1822
+        $this->ignoreLexiconAliases = $ignore;
1823
+    }
1824
+
1825
+    /**
1826
+     * Returns the installed versions of all apps
1827
+     *
1828
+     * @return array<string, string>
1829
+     */
1830
+    public function getAppInstalledVersions(bool $onlyEnabled = false): array {
1831
+        if ($this->appVersionsCache === null) {
1832
+            /** @var array<string, string> */
1833
+            $this->appVersionsCache = $this->searchValues('installed_version', false, IAppConfig::VALUE_STRING);
1834
+        }
1835
+        if ($onlyEnabled) {
1836
+            return array_filter(
1837
+                $this->appVersionsCache,
1838
+                fn (string $app): bool => $this->getValueString($app, 'enabled', 'no') !== 'no',
1839
+                ARRAY_FILTER_USE_KEY
1840
+            );
1841
+        }
1842
+        return $this->appVersionsCache;
1843
+    }
1844
+
1845
+    private function clearLocalCache(): void {
1846
+        $this->localCache?->remove(self::LOCAL_CACHE_KEY);
1847
+    }
1848 1848
 }
Please login to merge, or discard this patch.
Spacing   +31 added lines, -31 removed lines patch added patch discarded remove patch
@@ -59,11 +59,11 @@  discard block
 block discarded – undo
59 59
 	private const LOCAL_CACHE_TTL = 3;
60 60
 
61 61
 	/** @var array<string, array<string, string>> ['app_id' => ['config_key' => 'config_value']] */
62
-	private array $fastCache = [];   // cache for normal config keys
62
+	private array $fastCache = []; // cache for normal config keys
63 63
 	/** @var array<string, array<string, string>> ['app_id' => ['config_key' => 'config_value']] */
64
-	private array $lazyCache = [];   // cache for lazy config keys
64
+	private array $lazyCache = []; // cache for lazy config keys
65 65
 	/** @var array<string, array<string, int>> ['app_id' => ['config_key' => bitflag]] */
66
-	private array $valueTypes = [];  // type for all config values
66
+	private array $valueTypes = []; // type for all config values
67 67
 	private bool $fastLoaded = false;
68 68
 	private bool $lazyLoaded = false;
69 69
 	/** @var array<string, array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
@@ -85,7 +85,7 @@  discard block
 block discarded – undo
85 85
 		public readonly CacheFactory $cacheFactory,
86 86
 	) {
87 87
 		if ($config->getSystemValueBool('cache_app_config', true) && $cacheFactory->isLocalCacheAvailable()) {
88
-			$cacheFactory->withServerVersionPrefix(function (ICacheFactory $factory) {
88
+			$cacheFactory->withServerVersionPrefix(function(ICacheFactory $factory) {
89 89
 				$this->localCache = $factory->createLocal();
90 90
 			});
91 91
 		}
@@ -242,7 +242,7 @@  discard block
 block discarded – undo
242 242
 		$values = $this->formatAppValues($app, ($this->fastCache[$app] ?? []) + ($this->lazyCache[$app] ?? []));
243 243
 		$values = array_filter(
244 244
 			$values,
245
-			function (string $key) use ($prefix): bool {
245
+			function(string $key) use ($prefix): bool {
246 246
 				return str_starts_with($key, $prefix); // filter values based on $prefix
247 247
 			}, ARRAY_FILTER_USE_KEY
248 248
 		);
@@ -293,7 +293,7 @@  discard block
 block discarded – undo
293 293
 		$values = [];
294 294
 		foreach (array_keys($cache) as $app) {
295 295
 			if (isset($cache[$app][$key])) {
296
-				$values[$app] = $this->convertTypedValue($cache[$app][$key], $typedAs ?? $this->getValueType((string)$app, $key, $lazy));
296
+				$values[$app] = $this->convertTypedValue($cache[$app][$key], $typedAs ?? $this->getValueType((string) $app, $key, $lazy));
297 297
 			}
298 298
 		}
299 299
 
@@ -388,7 +388,7 @@  discard block
 block discarded – undo
388 388
 		int $default = 0,
389 389
 		bool $lazy = false,
390 390
 	): int {
391
-		return (int)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_INT);
391
+		return (int) $this->getTypedValue($app, $key, (string) $default, $lazy, self::VALUE_INT);
392 392
 	}
393 393
 
394 394
 	/**
@@ -406,7 +406,7 @@  discard block
 block discarded – undo
406 406
 	 * @see IAppConfig for explanation about lazy loading
407 407
 	 */
408 408
 	public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float {
409
-		return (float)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_FLOAT);
409
+		return (float) $this->getTypedValue($app, $key, (string) $default, $lazy, self::VALUE_FLOAT);
410 410
 	}
411 411
 
412 412
 	/**
@@ -670,7 +670,7 @@  discard block
 block discarded – undo
670 670
 		return $this->setTypedValue(
671 671
 			$app,
672 672
 			$key,
673
-			(string)$value,
673
+			(string) $value,
674 674
 			$lazy,
675 675
 			self::VALUE_INT | ($sensitive ? self::VALUE_SENSITIVE : 0)
676 676
 		);
@@ -700,7 +700,7 @@  discard block
 block discarded – undo
700 700
 		return $this->setTypedValue(
701 701
 			$app,
702 702
 			$key,
703
-			(string)$value,
703
+			(string) $value,
704 704
 			$lazy,
705 705
 			self::VALUE_FLOAT | ($sensitive ? self::VALUE_SENSITIVE : 0)
706 706
 		);
@@ -805,7 +805,7 @@  discard block
 block discarded – undo
805 805
 
806 806
 		$origValue = $value;
807 807
 		if ($sensitive || ($this->hasKey($app, $key, $lazy) && $this->isSensitive($app, $key, $lazy))) {
808
-			$value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
808
+			$value = self::ENCRYPTION_PREFIX.$this->crypto->encrypt($value);
809 809
 		}
810 810
 
811 811
 		if ($this->hasKey($app, $key, $lazy)) {
@@ -870,7 +870,7 @@  discard block
 block discarded – undo
870 870
 				} catch (AppConfigIncorrectTypeException) {
871 871
 					// can be ignored, this was just needed for a better exception message.
872 872
 				}
873
-				throw new AppConfigTypeConflictException('conflict between new type (' . $type . ') and old type (' . $currType . ')');
873
+				throw new AppConfigTypeConflictException('conflict between new type ('.$type.') and old type ('.$currType.')');
874 874
 			}
875 875
 
876 876
 			// we fix $type if the stored value, or the new value as it might be changed, is set as sensitive
@@ -1004,7 +1004,7 @@  discard block
 block discarded – undo
1004 1004
 		$value = $cache[$app][$key];
1005 1005
 		if ($sensitive) {
1006 1006
 			$type |= self::VALUE_SENSITIVE;
1007
-			$value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
1007
+			$value = self::ENCRYPTION_PREFIX.$this->crypto->encrypt($value);
1008 1008
 		} else {
1009 1009
 			$value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
1010 1010
 		}
@@ -1085,7 +1085,7 @@  discard block
 block discarded – undo
1085 1085
 			$typeString = $this->convertTypeToString($type);
1086 1086
 		} catch (AppConfigIncorrectTypeException $e) {
1087 1087
 			$this->logger->warning('type stored in database is not correct', ['exception' => $e, 'type' => $type]);
1088
-			$typeString = (string)$type;
1088
+			$typeString = (string) $type;
1089 1089
 		}
1090 1090
 
1091 1091
 		if (!isset($cache[$app][$key])) {
@@ -1133,7 +1133,7 @@  discard block
 block discarded – undo
1133 1133
 		try {
1134 1134
 			$lazy = false;
1135 1135
 			$this->matchAndApplyLexiconDefinition($app, $key, $lazy, lexiconEntry: $lexiconEntry);
1136
-		} catch (AppConfigTypeConflictException|AppConfigUnknownKeyException) {
1136
+		} catch (AppConfigTypeConflictException | AppConfigUnknownKeyException) {
1137 1137
 			// can be ignored
1138 1138
 		}
1139 1139
 
@@ -1168,7 +1168,7 @@  discard block
 block discarded – undo
1168 1168
 			'float' => IAppConfig::VALUE_FLOAT,
1169 1169
 			'boolean' => IAppConfig::VALUE_BOOL,
1170 1170
 			'array' => IAppConfig::VALUE_ARRAY,
1171
-			default => throw new AppConfigIncorrectTypeException('Unknown type ' . $type)
1171
+			default => throw new AppConfigIncorrectTypeException('Unknown type '.$type)
1172 1172
 		};
1173 1173
 	}
1174 1174
 
@@ -1189,7 +1189,7 @@  discard block
 block discarded – undo
1189 1189
 			IAppConfig::VALUE_FLOAT => 'float',
1190 1190
 			IAppConfig::VALUE_BOOL => 'boolean',
1191 1191
 			IAppConfig::VALUE_ARRAY => 'array',
1192
-			default => throw new AppConfigIncorrectTypeException('Unknown numeric type ' . $type)
1192
+			default => throw new AppConfigIncorrectTypeException('Unknown numeric type '.$type)
1193 1193
 		};
1194 1194
 	}
1195 1195
 
@@ -1298,11 +1298,11 @@  discard block
 block discarded – undo
1298 1298
 		}
1299 1299
 		if (strlen($app) > self::APP_MAX_LENGTH) {
1300 1300
 			throw new InvalidArgumentException(
1301
-				'Value (' . $app . ') for app is too long (' . self::APP_MAX_LENGTH . ')'
1301
+				'Value ('.$app.') for app is too long ('.self::APP_MAX_LENGTH.')'
1302 1302
 			);
1303 1303
 		}
1304 1304
 		if (strlen($configKey) > self::KEY_MAX_LENGTH) {
1305
-			throw new InvalidArgumentException('Value (' . $configKey . ') for key is too long (' . self::KEY_MAX_LENGTH . ')');
1305
+			throw new InvalidArgumentException('Value ('.$configKey.') for key is too long ('.self::KEY_MAX_LENGTH.')');
1306 1306
 		}
1307 1307
 		if ($valueType > -1) {
1308 1308
 			$valueType &= ~self::VALUE_SENSITIVE;
@@ -1324,7 +1324,7 @@  discard block
 block discarded – undo
1324 1324
 
1325 1325
 		// if lazy is null or true, we debug log
1326 1326
 		if ($lazy === true && $app !== null) {
1327
-			$exception = new \RuntimeException('The loading of lazy AppConfig values have been triggered by app "' . $app . '"');
1327
+			$exception = new \RuntimeException('The loading of lazy AppConfig values have been triggered by app "'.$app.'"');
1328 1328
 			$this->logger->debug($exception->getMessage(), ['exception' => $exception, 'app' => $app]);
1329 1329
 		}
1330 1330
 
@@ -1362,12 +1362,12 @@  discard block
 block discarded – undo
1362 1362
 		$rows = $result->fetchAll();
1363 1363
 		foreach ($rows as $row) {
1364 1364
 			// most of the time, 'lazy' is not in the select because its value is already known
1365
-			if ($lazy && ((int)$row['lazy']) === 1) {
1365
+			if ($lazy && ((int) $row['lazy']) === 1) {
1366 1366
 				$this->lazyCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1367 1367
 			} else {
1368 1368
 				$this->fastCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1369 1369
 			}
1370
-			$this->valueTypes[$row['appid']][$row['configkey']] = (int)($row['type'] ?? 0);
1370
+			$this->valueTypes[$row['appid']][$row['configkey']] = (int) ($row['type'] ?? 0);
1371 1371
 		}
1372 1372
 
1373 1373
 		$result->closeCursor();
@@ -1434,7 +1434,7 @@  discard block
 block discarded – undo
1434 1434
 		 * this solution would remove the loading of config values from disabled app
1435 1435
 		 * unless calling the method.
1436 1436
 		 */
1437
-		return $this->setTypedValue($app, $key, (string)$value, false, self::VALUE_MIXED);
1437
+		return $this->setTypedValue($app, $key, (string) $value, false, self::VALUE_MIXED);
1438 1438
 	}
1439 1439
 
1440 1440
 
@@ -1505,12 +1505,12 @@  discard block
 block discarded – undo
1505 1505
 	 *
1506 1506
 	 * @return string|int|float|bool|array
1507 1507
 	 */
1508
-	private function convertTypedValue(string $value, int $type): string|int|float|bool|array {
1508
+	private function convertTypedValue(string $value, int $type): string | int | float | bool | array {
1509 1509
 		switch ($type) {
1510 1510
 			case self::VALUE_INT:
1511
-				return (int)$value;
1511
+				return (int) $value;
1512 1512
 			case self::VALUE_FLOAT:
1513
-				return (float)$value;
1513
+				return (float) $value;
1514 1514
 			case self::VALUE_BOOL:
1515 1515
 				return in_array(strtolower($value), ['1', 'true', 'yes', 'on']);
1516 1516
 			case self::VALUE_ARRAY:
@@ -1684,7 +1684,7 @@  discard block
 block discarded – undo
1684 1684
 		?bool &$lazy = null,
1685 1685
 		int &$type = self::VALUE_MIXED,
1686 1686
 		?string &$default = null,
1687
-		?Entry &$lexiconEntry = null,
1687
+		?Entry & $lexiconEntry = null,
1688 1688
 	): bool {
1689 1689
 		if (in_array($key,
1690 1690
 			[
@@ -1701,7 +1701,7 @@  discard block
 block discarded – undo
1701 1701
 		}
1702 1702
 
1703 1703
 		if (!array_key_exists($key, $configDetails['entries'])) {
1704
-			return $this->applyLexiconStrictness($configDetails['strictness'], $app . '/' . $key);
1704
+			return $this->applyLexiconStrictness($configDetails['strictness'], $app.'/'.$key);
1705 1705
 		}
1706 1706
 
1707 1707
 		// if lazy is NULL, we ignore all check on the type/lazyness/default from Lexicon
@@ -1717,7 +1717,7 @@  discard block
 block discarded – undo
1717 1717
 		if ($type === self::VALUE_MIXED) {
1718 1718
 			$type = $appConfigValueType; // we overwrite if value was requested as mixed
1719 1719
 		} elseif ($appConfigValueType !== $type) {
1720
-			throw new AppConfigTypeConflictException('The app config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
1720
+			throw new AppConfigTypeConflictException('The app config key '.$app.'/'.$key.' is typed incorrectly in relation to the config lexicon');
1721 1721
 		}
1722 1722
 
1723 1723
 		$lazy = $lexiconEntry->isLazy();
@@ -1730,7 +1730,7 @@  discard block
 block discarded – undo
1730 1730
 			$type |= self::VALUE_SENSITIVE;
1731 1731
 		}
1732 1732
 		if ($lexiconEntry->isDeprecated()) {
1733
-			$this->logger->notice('App config key ' . $app . '/' . $key . ' is set as deprecated.');
1733
+			$this->logger->notice('App config key '.$app.'/'.$key.' is set as deprecated.');
1734 1734
 		}
1735 1735
 
1736 1736
 		return true;
@@ -1751,7 +1751,7 @@  discard block
 block discarded – undo
1751 1751
 			return true;
1752 1752
 		}
1753 1753
 
1754
-		$line = 'The app config key ' . $configAppKey . ' is not defined in the config lexicon';
1754
+		$line = 'The app config key '.$configAppKey.' is not defined in the config lexicon';
1755 1755
 		switch ($strictness) {
1756 1756
 			case Strictness::IGNORE:
1757 1757
 				return true;
Please login to merge, or discard this patch.
lib/public/DB/IResult.php 2 patches
Indentation   +110 added lines, -110 removed lines patch added patch discarded remove patch
@@ -31,114 +31,114 @@
 block discarded – undo
31 31
  */
32 32
 #[Consumable(since: '21.0.0')]
33 33
 interface IResult {
34
-	/**
35
-	 * @return true
36
-	 *
37
-	 * @since 21.0.0
38
-	 */
39
-	public function closeCursor(): bool;
40
-
41
-	/**
42
-	 * @param int $fetchMode
43
-	 *
44
-	 * @return mixed
45
-	 *
46
-	 * @since 21.0.0
47
-	 * @note Since 33.0.0, prefer using fetchAssociative/fetchNumeric/fetchOne or iterateAssociate/iterateNumeric instead.
48
-	 */
49
-	public function fetch(int $fetchMode = PDO::FETCH_ASSOC);
50
-
51
-	/**
52
-	 * Returns the next row of the result as an associative array or FALSE if there are no more rows.
53
-	 *
54
-	 * @return array<string, mixed>|false
55
-	 *
56
-	 * @since 33.0.0
57
-	 */
58
-	public function fetchAssociative(): array|false;
59
-
60
-	/**
61
-	 * Returns the next row of the result as a numeric array or FALSE if there are no more rows.
62
-	 *
63
-	 * @return list<mixed>|false
64
-	 *
65
-	 * @since 33.0.0
66
-	 */
67
-	public function fetchNumeric(): array|false;
68
-
69
-	/**
70
-	 * Returns the first value of the next row of the result or FALSE if there are no more rows.
71
-	 *
72
-	 * @return false|mixed
73
-	 *
74
-	 * @since 21.0.0
75
-	 */
76
-	public function fetchOne();
77
-
78
-	/**
79
-	 * @param int $fetchMode (one of PDO::FETCH_ASSOC, PDO::FETCH_NUM or PDO::FETCH_COLUMN (2, 3 or 7)
80
-	 *
81
-	 * @return mixed[]
82
-	 *
83
-	 * @since 21.0.0
84
-	 * @note Since 33.0.0, prefer using fetchAllAssociative/fetchAllNumeric/fetchFirstColumn or iterateAssociate/iterateNumeric instead.
85
-	 */
86
-	public function fetchAll(int $fetchMode = PDO::FETCH_ASSOC): array;
87
-
88
-	/**
89
-	 * Returns an array containing all the result rows represented as associative arrays.
90
-	 *
91
-	 * @return list<array<string,mixed>>
92
-	 * @since 33.0.0
93
-	 */
94
-	public function fetchAllAssociative(): array;
95
-
96
-	/**
97
-	 * Returns an array containing all the result rows represented as numeric arrays.
98
-	 *
99
-	 * @return list<list<mixed>>
100
-	 * @since 33.0.0
101
-	 */
102
-	public function fetchAllNumeric(): array;
103
-
104
-	/**
105
-	 * Returns the value of the first column of all rows.
106
-	 *
107
-	 * @return list<mixed>
108
-	 * @since 33.0.0
109
-	 */
110
-	public function fetchFirstColumn(): array;
111
-
112
-	/**
113
-	 * @return mixed
114
-	 *
115
-	 * @since 21.0.0
116
-	 * @deprecated 21.0.0 use \OCP\DB\IResult::fetchOne
117
-	 */
118
-	public function fetchColumn();
119
-
120
-	/**
121
-	 * @return int
122
-	 *
123
-	 * @since 21.0.0
124
-	 */
125
-	public function rowCount(): int;
126
-
127
-	/**
128
-	 * Returns an iterator over rows represented as numeric arrays.
129
-	 *
130
-	 * @return Traversable<list<mixed>>
131
-	 *
132
-	 * @since 33.0.0
133
-	 */
134
-	public function iterateNumeric(): Traversable;
135
-
136
-	/**
137
-	 * Returns an iterator over rows represented as associative arrays.
138
-	 *
139
-	 * @return Traversable<array<string,mixed>>
140
-	 *
141
-	 * @since 33.0.0
142
-	 */
143
-	public function iterateAssociative(): Traversable;
34
+    /**
35
+     * @return true
36
+     *
37
+     * @since 21.0.0
38
+     */
39
+    public function closeCursor(): bool;
40
+
41
+    /**
42
+     * @param int $fetchMode
43
+     *
44
+     * @return mixed
45
+     *
46
+     * @since 21.0.0
47
+     * @note Since 33.0.0, prefer using fetchAssociative/fetchNumeric/fetchOne or iterateAssociate/iterateNumeric instead.
48
+     */
49
+    public function fetch(int $fetchMode = PDO::FETCH_ASSOC);
50
+
51
+    /**
52
+     * Returns the next row of the result as an associative array or FALSE if there are no more rows.
53
+     *
54
+     * @return array<string, mixed>|false
55
+     *
56
+     * @since 33.0.0
57
+     */
58
+    public function fetchAssociative(): array|false;
59
+
60
+    /**
61
+     * Returns the next row of the result as a numeric array or FALSE if there are no more rows.
62
+     *
63
+     * @return list<mixed>|false
64
+     *
65
+     * @since 33.0.0
66
+     */
67
+    public function fetchNumeric(): array|false;
68
+
69
+    /**
70
+     * Returns the first value of the next row of the result or FALSE if there are no more rows.
71
+     *
72
+     * @return false|mixed
73
+     *
74
+     * @since 21.0.0
75
+     */
76
+    public function fetchOne();
77
+
78
+    /**
79
+     * @param int $fetchMode (one of PDO::FETCH_ASSOC, PDO::FETCH_NUM or PDO::FETCH_COLUMN (2, 3 or 7)
80
+     *
81
+     * @return mixed[]
82
+     *
83
+     * @since 21.0.0
84
+     * @note Since 33.0.0, prefer using fetchAllAssociative/fetchAllNumeric/fetchFirstColumn or iterateAssociate/iterateNumeric instead.
85
+     */
86
+    public function fetchAll(int $fetchMode = PDO::FETCH_ASSOC): array;
87
+
88
+    /**
89
+     * Returns an array containing all the result rows represented as associative arrays.
90
+     *
91
+     * @return list<array<string,mixed>>
92
+     * @since 33.0.0
93
+     */
94
+    public function fetchAllAssociative(): array;
95
+
96
+    /**
97
+     * Returns an array containing all the result rows represented as numeric arrays.
98
+     *
99
+     * @return list<list<mixed>>
100
+     * @since 33.0.0
101
+     */
102
+    public function fetchAllNumeric(): array;
103
+
104
+    /**
105
+     * Returns the value of the first column of all rows.
106
+     *
107
+     * @return list<mixed>
108
+     * @since 33.0.0
109
+     */
110
+    public function fetchFirstColumn(): array;
111
+
112
+    /**
113
+     * @return mixed
114
+     *
115
+     * @since 21.0.0
116
+     * @deprecated 21.0.0 use \OCP\DB\IResult::fetchOne
117
+     */
118
+    public function fetchColumn();
119
+
120
+    /**
121
+     * @return int
122
+     *
123
+     * @since 21.0.0
124
+     */
125
+    public function rowCount(): int;
126
+
127
+    /**
128
+     * Returns an iterator over rows represented as numeric arrays.
129
+     *
130
+     * @return Traversable<list<mixed>>
131
+     *
132
+     * @since 33.0.0
133
+     */
134
+    public function iterateNumeric(): Traversable;
135
+
136
+    /**
137
+     * Returns an iterator over rows represented as associative arrays.
138
+     *
139
+     * @return Traversable<array<string,mixed>>
140
+     *
141
+     * @since 33.0.0
142
+     */
143
+    public function iterateAssociative(): Traversable;
144 144
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -55,7 +55,7 @@  discard block
 block discarded – undo
55 55
 	 *
56 56
 	 * @since 33.0.0
57 57
 	 */
58
-	public function fetchAssociative(): array|false;
58
+	public function fetchAssociative(): array | false;
59 59
 
60 60
 	/**
61 61
 	 * Returns the next row of the result as a numeric array or FALSE if there are no more rows.
@@ -64,7 +64,7 @@  discard block
 block discarded – undo
64 64
 	 *
65 65
 	 * @since 33.0.0
66 66
 	 */
67
-	public function fetchNumeric(): array|false;
67
+	public function fetchNumeric(): array | false;
68 68
 
69 69
 	/**
70 70
 	 * Returns the first value of the next row of the result or FALSE if there are no more rows.
Please login to merge, or discard this patch.