1<?php 2/** 3 * @author Arthur Schiwon <blizzz@arthur-schiwon.de> 4 * @author Bart Visscher <bartv@thisnet.nl> 5 * @author Jakob Sack <mail@jakobsack.de> 6 * @author Joas Schilling <coding@schilljs.com> 7 * @author Jörn Friedrich Dreyer <jfd@butonic.de> 8 * @author Morris Jobke <hey@morrisjobke.de> 9 * @author Robin Appelman <icewind@owncloud.com> 10 * @author Robin McCorkell <robin@mccorkell.me.uk> 11 * @author Thomas Müller <thomas.mueller@tmit.eu> 12 * 13 * @copyright Copyright (c) 2018, ownCloud GmbH 14 * @license AGPL-3.0 15 * 16 * This code is free software: you can redistribute it and/or modify 17 * it under the terms of the GNU Affero General Public License, version 3, 18 * as published by the Free Software Foundation. 19 * 20 * This program is distributed in the hope that it will be useful, 21 * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 * GNU Affero General Public License for more details. 24 * 25 * You should have received a copy of the GNU Affero General Public License, version 3, 26 * along with this program. If not, see <http://www.gnu.org/licenses/> 27 * 28 */ 29 30namespace OC; 31 32use OCP\Events\EventEmitterTrait; 33use OCP\IAppConfig; 34use OCP\IDBConnection; 35 36/** 37 * This class provides an easy way for apps to store config values in the 38 * database. 39 */ 40class AppConfig implements IAppConfig { 41 use EventEmitterTrait; 42 /** @var \OCP\IDBConnection $conn */ 43 protected $conn; 44 45 private $cache = []; 46 47 private $configLoaded; 48 49 /** 50 * @param IDBConnection $conn 51 */ 52 public function __construct(IDBConnection $conn) { 53 $this->conn = $conn; 54 $this->configLoaded = false; 55 } 56 57 /** 58 * @param string $app 59 * @return array 60 */ 61 private function getAppValues($app) { 62 $this->loadConfigValues(); 63 64 if (isset($this->cache[$app])) { 65 return $this->cache[$app]; 66 } 67 68 return []; 69 } 70 71 /** 72 * Get all apps using the config 73 * 74 * @return array an array of app ids 75 * 76 * This function returns a list of all apps that have at least one 77 * entry in the appconfig table. 78 */ 79 public function getApps() { 80 $this->loadConfigValues(); 81 82 return $this->getSortedKeys($this->cache); 83 } 84 85 /** 86 * Get the available keys for an app 87 * 88 * @param string $app the app we are looking for 89 * @return array an array of key names 90 * 91 * This function gets all keys of an app. Please note that the values are 92 * not returned. 93 */ 94 public function getKeys($app) { 95 $this->loadConfigValues(); 96 97 if (isset($this->cache[$app])) { 98 return $this->getSortedKeys($this->cache[$app]); 99 } 100 101 return []; 102 } 103 104 public function getSortedKeys($data) { 105 $keys = \array_keys($data); 106 \sort($keys); 107 return $keys; 108 } 109 110 /** 111 * Gets the config value 112 * 113 * @param string $app app 114 * @param string $key key 115 * @param string $default = null, default value if the key does not exist 116 * @return string the value or $default 117 * 118 * This function gets a value from the appconfig table. If the key does 119 * not exist the default value will be returned 120 */ 121 public function getValue($app, $key, $default = null) { 122 $this->loadConfigValues(); 123 124 if ($this->hasKey($app, $key)) { 125 return $this->cache[$app][$key]; 126 } 127 128 return $default; 129 } 130 131 /** 132 * check if a key is set in the appconfig 133 * 134 * @param string $app 135 * @param string $key 136 * @return bool 137 */ 138 public function hasKey($app, $key) { 139 $this->loadConfigValues(); 140 141 return isset($this->cache[$app][$key]); 142 } 143 144 /** 145 * Sets a value. If the key did not exist before it will be created. 146 * 147 * @param string $app app 148 * @param string $key key 149 * @param string|float|int $value value 150 * @return bool True if the value was inserted or updated, false if the value was the same 151 */ 152 public function setValue($app, $key, $value) { 153 return $this->emittingCall(function (&$afterArray) use (&$app, &$key, &$value) { 154 if (!$this->hasKey($app, $key)) { 155 $inserted = (bool) $this->conn->insertIfNotExist('*PREFIX*appconfig', [ 156 'appid' => $app, 157 'configkey' => $key, 158 'configvalue' => $value, 159 ], [ 160 'appid', 161 'configkey', 162 ]); 163 164 if ($inserted) { 165 if (!isset($this->cache[$app])) { 166 $this->cache[$app] = []; 167 } 168 169 $this->cache[$app][$key] = $value; 170 return true; 171 } 172 } 173 174 $sql = $this->conn->getQueryBuilder(); 175 $sql->update('appconfig') 176 ->set('configvalue', $sql->createParameter('configvalue')) 177 ->where($sql->expr()->eq('appid', $sql->createParameter('app'))) 178 ->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey'))) 179 ->setParameter('configvalue', $value) 180 ->setParameter('app', $app) 181 ->setParameter('configkey', $key); 182 183 /* 184 * Only limit to the existing value for non-Oracle DBs: 185 * http://docs.oracle.com/cd/E11882_01/server.112/e26088/conditions002.htm#i1033286 186 * > Large objects (LOBs) are not supported in comparison conditions. 187 */ 188 if (!($this->conn instanceof \OC\DB\OracleConnection)) { 189 // Only update the value when it is not the same 190 $sql->andWhere($sql->expr()->neq('configvalue', $sql->createParameter('configvalue'))) 191 ->setParameter('configvalue', $value); 192 } 193 194 if (isset($this->cache[$app], $this->cache[$app][$key])) { 195 $afterArray['update'] = true; 196 $afterArray['oldvalue'] = $this->cache[$app][$key]; 197 } 198 199 $changedRow = (bool) $sql->execute(); 200 201 $this->cache[$app][$key] = $value; 202 203 return $changedRow; 204 }, [ 205 'before' => ['key' => $key, 'value' => $value, 'app' => $app], 206 'after' => ['key' => $key, 'value' => $value, 'app' => $app, 'update' => false, 'oldvalue' => null] 207 ], 'appconfig', 'setvalue'); 208 } 209 210 /** 211 * Deletes a key 212 * 213 * @param string $app app 214 * @param string $key key 215 * @return boolean|null 216 */ 217 public function deleteKey($app, $key) { 218 $this->emittingCall(function () use (&$app, &$key) { 219 $this->loadConfigValues(); 220 221 $sql = $this->conn->getQueryBuilder(); 222 $sql->delete('appconfig') 223 ->where($sql->expr()->eq('appid', $sql->createParameter('app'))) 224 ->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey'))) 225 ->setParameter('app', $app) 226 ->setParameter('configkey', $key); 227 $sql->execute(); 228 229 unset($this->cache[$app][$key]); 230 return true; 231 }, [ 232 'before' => ['app' => $app, 'key' => $key], 233 'after' => ['app' => $app, 'key' => $key] 234 ], 'appconfig', 'deletevalue'); 235 } 236 237 /** 238 * Remove app from appconfig 239 * 240 * @param string $app app 241 * @return boolean|null 242 * 243 * Removes all keys in appconfig belonging to the app. 244 */ 245 public function deleteApp($app) { 246 $this->emittingCall(function () use (&$app) { 247 $this->loadConfigValues(); 248 249 $sql = $this->conn->getQueryBuilder(); 250 $sql->delete('appconfig') 251 ->where($sql->expr()->eq('appid', $sql->createParameter('app'))) 252 ->setParameter('app', $app); 253 $sql->execute(); 254 255 unset($this->cache[$app]); 256 return true; 257 }, [ 258 'before' => ['app' => $app], 259 'after' => ['app' => $app] 260 ], 'appconfig', 'deleteapp'); 261 } 262 263 /** 264 * get multiple values, either the app or key can be used as wildcard by setting it to false 265 * 266 * @param string|false $app 267 * @param string|false $key 268 * @return array|false 269 */ 270 public function getValues($app, $key) { 271 if (($app !== false) === ($key !== false)) { 272 return false; 273 } 274 275 if ($key === false) { 276 return $this->getAppValues($app); 277 } else { 278 $appIds = $this->getApps(); 279 $values = \array_map(function ($appId) use ($key) { 280 return isset($this->cache[$appId][$key]) ? $this->cache[$appId][$key] : null; 281 }, $appIds); 282 $result = \array_combine($appIds, $values); 283 284 return \array_filter($result); 285 } 286 } 287 288 /** 289 * Load all the app config values 290 */ 291 protected function loadConfigValues() { 292 if ($this->configLoaded) { 293 return; 294 } 295 296 $this->cache = []; 297 298 $sql = $this->conn->getQueryBuilder(); 299 $sql->select('*') 300 ->from('appconfig'); 301 $result = $sql->execute(); 302 303 // we are going to store the result in memory anyway 304 $rows = $result->fetchAll(); 305 foreach ($rows as $row) { 306 if (!isset($this->cache[$row['appid']])) { 307 $this->cache[$row['appid']] = []; 308 } 309 310 // check if installed_version matches the pattern 311 // one_or_more_digits-dot-one_or_more_digits-any-other-characters 312 if ($row['configkey'] === 'installed_version' 313 && \preg_match('/\d+\.\d+.*$/', $row['configvalue']) !== 1 314 ) { 315 $row['configvalue'] = '0.0.1'; 316 } 317 $this->cache[$row['appid']][$row['configkey']] = $row['configvalue']; 318 } 319 $result->closeCursor(); 320 321 $this->configLoaded = true; 322 } 323} 324