<?php
declare(strict_types=1);
/**
* Get or set simple key-value-pairs, backed by the database
* to make them persistent.
*/
class Property
{
private static $cache = false;
/**
* Retrieve value from property store.
*
* @param string $key key to retrieve the value of
* @param mixed $default value to return if $key does not exist in the property store
* @return mixed the value attached to $key, or $default if $key does not exist
*/
public static function get(string $key, $default = false)
{
if (self::$cache === false) {
self::$cache = [];
$NOW = time();
$res = Database::simpleQuery("SELECT name, dateline, value FROM property");
foreach ($res as $row) {
if ($row['dateline'] != 0 && $row['dateline'] < $NOW)
continue;
self::$cache[$row['name']] = $row['value'];
}
}
if (!isset(self::$cache[$key]))
return $default;
return self::$cache[$key];
}
/**
* Set value in property store. Passing null or false as the value deletes the
* entry from the property table.
*
* @param string $key key of value to set
* @param string|null|false $value the value to store for $key
* @param int $maxAgeMinutes how long to keep this entry around at least, in minutes. 0 for infinite
*/
public static function set(string $key, $value, int $maxAgeMinutes = 0): void
{
if ($value === false || $value === null) {
Database::exec("DELETE FROM property WHERE name = :key", ['key' => $key]);
if (self::$cache !== false) {
unset(self::$cache[$key]);
}
} else {
Database::exec("INSERT INTO property (name, value, dateline) VALUES (:key, :value, :dateline)"
. " ON DUPLICATE KEY UPDATE value = VALUES(value), dateline = VALUES(dateline)", [
'key' => $key,
'value' => $value,
'dateline' => ($maxAgeMinutes === 0 ? 0 : time() + ($maxAgeMinutes * 60))
]);
if (self::$cache !== false) {
self::$cache[$key] = $value;
}
}
}
/**
* Retrieve property list from the store.
*
* @param string $key Key of list to get all items for
* @return array All the items matching the key
*/
public static function getList(string $key): array
{
$res = Database::simpleQuery("SELECT subkey, dateline, value FROM property_list
WHERE `name` = :key", compact('key'));
$NOW = time();
$return = [];
foreach ($res as $row) {
if ($row['dateline'] != 0 && $row['dateline'] < $NOW)
continue;
$return[$row['subkey']] = $row['value'];
}
return $return;
}
/**
* @return ?string entry from property list
*/
public static function getListEntry(string $key, int $subkey): ?string
{
$row = Database::queryFirst("SELECT dateline, `value` FROM property_list
WHERE `name` = :key AND subkey = :subkey", ['key' => $key, 'subkey' => $subkey]);
if ($row === false || ($row['dateline'] != 0 && $row['dateline'] < time()))
return null;
return $row['value'];
}
/**
* Add item to property list.
*
* @param string $key key of value to set
* @param string $value the value to add for $key
* @param int $maxAgeMinutes how long to keep this entry around at least, in minutes. 0 for infinite
* @return int The auto generated sub-key
*/
public static function addToList(string $key, string $value, int $maxAgeMinutes = 0): int
{
Database::exec("INSERT INTO property_list (name, value, dateline) VALUES (:key, :value, :dateline)", array(
'key' => $key,
'value' => $value,
'dateline' => ($maxAgeMinutes === 0 ? 0 : time() + ($maxAgeMinutes * 60))
));
return Database::lastInsertId();
}
/**
* Update existing entry in property list.
*
* @param string $key key of list
* @param int $subkey subkey of entry in list
* @param string $value new value to set entry to
* @param int $maxAgeMinutes the new lifetime of that entry
* @param ?string $expectedValue if not null, the value will only be updated if it currently has this value
* @return bool whether the entry existed and has been updated
*/
public static function updateListEntry(string $key, int $subkey, string $value,
int $maxAgeMinutes = 0, string $expectedValue = null): bool
{
$args = [
'name' => $key,
'subkey' => $subkey,
'newvalue' => $value,
'dateline' => ($maxAgeMinutes === 0 ? 0 : time() + ($maxAgeMinutes * 60)),
];
if ($expectedValue !== null) {
$args['oldvalue'] = $expectedValue;
return Database::exec("UPDATE property_list SET `value` = :newvalue, dateline = :dateline
WHERE `name` = :name AND subkey = :subkey AND `value` = :oldvalue", $args) > 0;
}
return Database::exec("UPDATE property_list SET `value` = :newvalue, dateline = :dateline
WHERE `name` = :name AND subkey = :subkey", $args) > 0;
}
/**
* Remove given item from property list. If the list contains this item
* multiple times, they will all be removed.
*
* @param string $key Key of list
* @param string $value item to remove
* @return int number of items removed
*/
public static function removeFromListByVal(string $key, string $value): int
{
return Database::exec("DELETE FROM property_list WHERE name = :key AND value = :value", array(
'key' => $key,
'value' => $value,
));
}
/**
* Remove given item from property list. If the list contains this item
* multiple times, they will all be removed.
*
* @param string $key Key of list
* @param int $value item to remove
* @return bool whether item was found and removed
*/
public static function removeFromListByKey(string $key, int $subkey): bool
{
return Database::exec("DELETE FROM property_list WHERE name = :key AND subkey = :subkey", array(
'key' => $key,
'subkey' => $subkey,
)) > 0;
}
/**
* Delete entire list with given key.
*
* @param string $key Key of list
* @return int number of items removed
*/
public static function clearList(string $key): int
{
return Database::exec("DELETE FROM property_list WHERE name = :key", compact('key'));
}
/*
* Legacy getters/setters
*/
public static function getServerIp(): string
{
return self::get('server-ip', 'invalid');
}
public static function setServerIp(string $value, bool $automatic = false): bool
{
if ($value === self::getServerIp())
return false;
EventLog::info('Server IP changed from ' . self::getServerIp() . ' to ' . $value . ($automatic ? ' (auto detected)' : ''));
self::set('server-ip', $value);
Event::serverIpChanged();
return true;
}
public static function getVmStoreConfig(): array
{
$data = self::get('vmstore-config');
if (!is_string($data))
return [];
$data = json_decode($data, true);
if (!is_array($data))
return [];
return $data;
}
public static function getVmStoreUrl()
{
$store = self::getVmStoreConfig();
if (!isset($store['storetype']))
return false;
if ($store['storetype'] === 'nfs')
return $store['nfsaddr'];
if ($store['storetype'] === 'cifs')
return $store['cifsaddr'];
if ($store['storetype'] === 'internal')
return '<local>';
return '<unknown>';
}
public static function setVmStoreConfig($value)
{
self::set('vmstore-config', json_encode($value));
}
public static function setLastWarningId($id)
{
self::set('last-warn-event-id', $id);
}
public static function getLastWarningId()
{
return self::get('last-warn-event-id', 0);
}
public static function setNeedsSetup(bool $value)
{
self::set('needs-setup', (int)$value);
}
public static function getNeedsSetup(): bool
{
return self::get('needs-setup') != 0;
}
public static function setPasswordFieldType(string $value)
{
self::set('password-type', $value);
}
public static function getPasswordFieldType(): string
{
return self::get('password-type', 'password');
}
}