diff options
| author | Philipp Schüttlöffel <schuettloeffel@zqs.uni-hannover.de> | 2024-09-24 10:53:31 +0200 |
|---|---|---|
| committer | Philipp Schüttlöffel <schuettloeffel@zqs.uni-hannover.de> | 2024-09-24 10:53:31 +0200 |
| commit | 4459dd7917f4d1c34f40bb68f0e991e9c3d53e4c (patch) | |
| tree | 5c07151ae61276d334e88f6309c30d439a85c12e /lib/classes/Config.php | |
| parent | da0022e5c1abbf9825ae76debaabdff7e8623bb4 (diff) | |
| parent | 97a188592c679890a25c37ab78463add76a52ff7 (diff) | |
Merge branch 'main' into issue-3911issue-3911
Diffstat (limited to 'lib/classes/Config.php')
| -rw-r--r-- | lib/classes/Config.php | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/lib/classes/Config.php b/lib/classes/Config.php new file mode 100644 index 0000000..c49a845 --- /dev/null +++ b/lib/classes/Config.php @@ -0,0 +1,469 @@ +<?php +/** + * Config.php + * provides access to global configuration + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author André Noack <noack@data-quest.de> + * @copyright 2010 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP +*/ + +class Config implements ArrayAccess, Countable, IteratorAggregate +{ + private static $instance = null; + + /** + * contains all config entries as field => value pairs + * @var array + */ + protected $data = []; + /** + * contains additional metadata for config fields + * @var array + */ + protected $metadata = []; + + /** + * returns singleton instance + * @return Config + */ + public static function get() + { + if (self::$instance === null) { + $config = new Config(); + self::$instance = $config; + } + return self::$instance; + } + + /** + * alias of Config::get() for compatibility + * @return Config + */ + public static function getInstance() + { + return self::get(); + } + + /** + * use to set singleton instance for testing + * or to unset by passing null + * @param Config $my_instance + */ + public static function set() + { + $my_instance = func_get_arg(0); + self::$instance = $my_instance; + } + + /** + * pass array of config entries in field => value pairs + * to circumvent fetching from database + * @param array $data + */ + public function __construct($data = null) + { + $this->fetchData($data); + } + + /** + * returns a list of config entry names, filtered by + * given params + * @param string filter by range: global, range, user, course or institute + * @param string filter by section + * @param string filter by part of name + * @return array + */ + public function getFields($range = null, $section = null, $name = null) + { + if ($range && !in_array($range, words('global range user course institute'))) { + throw new Exception('Invalid range type'); + } + + $temp = $this->metadata; + + if ($range) { + $temp = array_filter($temp, function ($a) use ($range) { + return $a['range'] === $range + || ($a['range'] === 'range' && in_array($range, words('user course institute'))); + }); + } + if ($section) { + $temp = array_filter($temp, function ($a) use ($section) { + return $a['section'] === $section; + }); + } + if ($name) { + $temp = array_filter($temp, function ($a) use ($name) { + return mb_stripos($a['field'], $name) !== false; + }); + } + + return array_keys($temp); + } + + /** + * returns metadata for config entry + * @param string $field + * @return array + */ + public function getMetadata($field) + { + return $this->metadata[$field] ?? []; + } + + /** + * returns value of config entry + * for compatibility reasons an existing variable in global + * namespace with the same name is also returned + * @param string $field + * @return mixed + */ + public function getValue($field) + { + if ($this->fromEnv($field)) { + return $_ENV["STUDIP_CONFIG_{$field}"]; + } + + if (array_key_exists($field, $this->data)) { + return $this->data[$field]; + } + + if (isset($GLOBALS[$field]) && !isset($_REQUEST[$field])) { + return $GLOBALS[$field]; + } + + return null; + } + + /** + * set config entry to given value, but don't store it + * in database + * @param string $field + * @param mixed $value + * @return + */ + public function setValue($field, $value) + { + if (array_key_exists($field, $this->data)) { + return $this->data[$field] = $value; + } + } + + /** + * IteratorAggregate + */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->data); + } + + /** + * magic method for dynamic properties + */ + public function __get($field) + { + return $this->getValue($field); + } + + /** + * magic method for dynamic properties + */ + public function __set($field, $value) + { + return $this->setValue($field, $value); + } + + /** + * magic method for dynamic properties + */ + public function __isset($field) + { + return isset($this->data[$field]); + } + + /** + * ArrayAccess: Check whether the given offset exists. + */ + public function offsetExists($offset): bool + { + return isset($this->$offset); + } + + /** + * ArrayAccess: Get the value at the given offset. + */ + public function offsetGet($offset): mixed + { + return $this->$offset; + } + + /** + * ArrayAccess: Set the value at the given offset. + */ + public function offsetSet($offset, $value): void + { + $this->$offset = $value; + } + + /** + * ArrayAccess: unset the value at the given offset (not applicable) + */ + public function offsetUnset($offset): void + { + + } + + /** + * Countable + */ + public function count(): int + { + return count($this->data); + } + + /** + * fetch config data from table config + * pass array to override database access + * @param array $data + */ + protected function fetchData($data = null) + { + if ($data !== null) { + $this->data = $data; + } else { + $this->data = []; + $db = DBManager::get(); + + try { + $query = "SELECT config.field, IFNULL(config_values.value, config.value) AS value, type, section, `range`, description, + config_values.comment, config_values.value = config.value AS is_default + FROM config + LEFT JOIN config_values ON config.field = config_values.field AND range_id = 'studip' + ORDER BY section, config.field"; + $rs = $db->query($query); + } catch (Exception $e) { + //if migration is smaller than 226 and Stud.IP needs to be migrated to version 4.1 or greater: + $query = "SELECT field, value, type, section, `range`, description, comment, is_default + FROM `config` + ORDER BY is_default DESC, section, field"; + $rs = $db->query($query); + } + + while ($row = $rs->fetch(PDO::FETCH_ASSOC)) { + // set the the type of the default entry for the modified entry + if (!empty($this->metadata[$row['field']])) { + $row['type'] = $this->metadata[$row['field']]['type']; + } + + $this->data[$row['field']] = $this->convertFromDatabase( + $row['type'], + $row['value'], + $row['field'] + ); + + $this->metadata[$row['field']] = array_intersect_key($row, array_flip(words('type section range description is_default comment'))); + $this->metadata[$row['field']]['field'] = $row['field']; + $this->metadata[$row['field']]['type'] = $row['type'] ?: 'string'; + } + } + } + + /** + * store new value for existing config entry in database + * posts notification ConfigValueChanged if entry is changed + * @param string $field + * @param string $data + * @throws InvalidArgumentException + * @return boolean + */ + public function store($field, $data) + { + if (!is_array($data) || !isset($data['value'])) { + $values['value'] = $data; + } else { + $values = $data; + } + + $values['value'] = $this->convertForDatabase( + $this->metadata[$field]['type'], + $values['value'], + $field + ); + + $entry = ConfigEntry::find($field); + if (!isset($entry)) { + throw new InvalidArgumentException($field . " not found in config table"); + } + $ret = 0; + if (isset($values['value'])) { + $value_entry = new ConfigValue([$field, 'studip']); + $old_value = $value_entry->isNew() ? $entry->value : $value_entry->value; + $value_entry->value = $values['value']; + if (isset($values['comment'])) { + $value_entry->comment = $values['comment']; + } + if ($entry->isDefault($value_entry)) { + $ret += $value_entry->delete(); + } else { + $ret += $value_entry->store(); + } + } + + if (isset($values['section'])) { + $entry->section = $values['section']; + $ret += $entry->store(); + } + + if ($ret) { + $this->fetchData(); + if (isset($value_entry)) { + NotificationCenter::postNotification('ConfigValueDidChange', $this, [ + 'field' => $field, + 'old_value' => $old_value, + 'new_value' => $value_entry->value, + ]); + } + } + return $ret > 0; + } + + /** + * creates a new config entry in database + * @param string name of entry + * @param array data to insert as assoc array + * @throws InvalidArgumentException + * @return null|ConfigEntry + */ + public function create($field, $data = []) + { + if (!$field) { + throw new InvalidArgumentException("config fieldname is mandatory"); + } + $entry = new ConfigEntry($field); + if (!$entry->isNew()) { + throw new InvalidArgumentException("config $field already exists"); + } + $entry->setData($data); + $ret = $entry->store() ? $entry : null; + if ($ret) { + $this->fetchData(); + } + return $ret; + } + + /** + * delete config entry from database + * @param string name of entry + * @throws InvalidArgumentException + * @return integer number of deleted rows + */ + public function delete($field) + { + if (!$field) { + throw new InvalidArgumentException("config fieldname is mandatory"); + } + ConfigValue::deleteBySql('field=?', [$field]); + $deleted = ConfigEntry::deleteBySql('field=?', [$field]); + if ($deleted) { + $this->fetchData(); + } + return $deleted; + } + + /** + * Returns the identifier for the i18n field. + * @param string $field + * @return string + */ + protected function getI18NIdentifier($field) + { + return md5($field); + } + + /** + * Transforms the data from the database for use. + * + * @param string $type + * @param mixed $value + * @param string $field + * @return mixed + */ + public function convertFromDatabase($type, $value, $field) + { + if ($type === 'integer') { + return (int) $value; + } + + if ($type === 'boolean') { + return (bool) $value; + } + + if ($type === 'array') { + return (array) json_decode($value, true); + } + + if ($type === 'i18n') { + return new I18NString($value, null, [ + 'object_id' => $this->getI18NIdentifier($field), + 'table' => 'config', + 'field' => 'value', + ]); + } + + return (string) $value; + } + + /** + * Transforms the given value to be stored in the database. + * + * @param string $type + * @param mixed $value + * @param string $field + * @return mixed + */ + public function convertForDatabase($type, $value, $field) + { + if ($type === 'boolean') { + return (bool) $value; + } + + if ($type === 'integer') { + return (int) $value; + } + + if ($type === 'array') { + return json_encode($value); + } + + if ($type === 'i18n') { + $value->setMetadata([ + 'object_id' => $this->getI18NIdentifier($field), + 'table' => 'config', + 'field' => 'value', + ]); + $value->storeTranslations(); + + return $value->original(); + } + + return (string) $value; + } + + /** + * Returns whether the value was set from the environment. + * + * @param string $field + * @return bool + */ + public function fromEnv(string $field): bool + { + return isset($_ENV["STUDIP_CONFIG_{$field}"]); + } +} |
