From d760d339895d40e9de86ab98471d233179502a8a Mon Sep 17 00:00:00 2001
From: Hypolite Petovan <mrpetovan@gmail.com>
Date: Sat, 3 Mar 2018 12:09:12 -0500
Subject: [PATCH] Add Config adapter interfaces/classes

---
 src/Core/Config/IConfigAdapter.php        |  72 ++++++++++++
 src/Core/Config/IPConfigAdapter.php       |  77 +++++++++++++
 src/Core/Config/JITConfigAdapter.php      | 132 ++++++++++++++++++++++
 src/Core/Config/JITPConfigAdapter.php     | 117 +++++++++++++++++++
 src/Core/Config/PreloadConfigAdapter.php  | 119 +++++++++++++++++++
 src/Core/Config/PreloadPConfigAdapter.php | 102 +++++++++++++++++
 6 files changed, 619 insertions(+)
 create mode 100644 src/Core/Config/IConfigAdapter.php
 create mode 100644 src/Core/Config/IPConfigAdapter.php
 create mode 100644 src/Core/Config/JITConfigAdapter.php
 create mode 100644 src/Core/Config/JITPConfigAdapter.php
 create mode 100644 src/Core/Config/PreloadConfigAdapter.php
 create mode 100644 src/Core/Config/PreloadPConfigAdapter.php

diff --git a/src/Core/Config/IConfigAdapter.php b/src/Core/Config/IConfigAdapter.php
new file mode 100644
index 0000000000..ee5ca3ca54
--- /dev/null
+++ b/src/Core/Config/IConfigAdapter.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Friendica\Core\Config;
+
+/**
+ *
+ * @author Hypolite Petovan <mrpetovan@gmail.com>
+ */
+interface IConfigAdapter
+{
+	/**
+	 * @brief Loads all configuration values into a cached storage.
+	 *
+	 * All configuration values of the system are stored in global cache
+	 * which is available under the global variable $a->config
+	 *
+	 * @param string  $cat The category of the configuration values to load
+	 *
+	 * @return void
+	 */
+	public function load($cat = "config");
+
+	/**
+	 * @brief Get a particular user's config variable given the category name
+	 * ($family) and a key.
+	 *
+	 * Get a particular config value from the given category ($family)
+	 * and the $key from a cached storage in $a->config[$uid].
+	 * $instore is only used by the set_config function
+	 * to determine if the key already exists in the DB
+	 * If a key is found in the DB but doesn't exist in
+	 * local config cache, pull it into the cache so we don't have
+	 * to hit the DB again for this item.
+	 *
+	 * @param string  $cat           The category of the configuration value
+	 * @param string  $k             The configuration key to query
+	 * @param mixed   $default_value optional, The value to return if key is not set (default: null)
+	 * @param boolean $refresh       optional, If true the config is loaded from the db and not from the cache (default: false)
+	 *
+	 * @return mixed Stored value or null if it does not exist
+	 */
+	public function get($cat, $k, $default_value = null, $refresh = false);
+
+	/**
+	 * @brief Sets a configuration value for system config
+	 *
+	 * Stores a config value ($value) in the category ($family) under the key ($key)
+	 * for the user_id $uid.
+	 *
+	 * Note: Please do not store booleans - convert to 0/1 integer values!
+	 *
+	 * @param string $family The category of the configuration value
+	 * @param string $key    The configuration key to set
+	 * @param mixed  $value  The value to store
+	 *
+	 * @return mixed Stored $value or false if the database update failed
+	 */
+	public function set($cat, $k, $value);
+
+	/**
+	 * @brief Deletes the given key from the system configuration.
+	 *
+	 * Removes the configured value from the stored cache in $a->config
+	 * and removes it from the database.
+	 *
+	 * @param string $cat The category of the configuration value
+	 * @param string $k   The configuration key to delete
+	 *
+	 * @return mixed
+	 */
+	public function delete($cat, $k);
+}
diff --git a/src/Core/Config/IPConfigAdapter.php b/src/Core/Config/IPConfigAdapter.php
new file mode 100644
index 0000000000..f78654d39c
--- /dev/null
+++ b/src/Core/Config/IPConfigAdapter.php
@@ -0,0 +1,77 @@
+<?php
+
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+namespace Friendica\Core\Config;
+
+/**
+ *
+ * @author benlo
+ */
+interface IPConfigAdapter
+{
+	/**
+	 * @brief Loads all configuration values of a user's config family into a cached storage.
+	 *
+	 * All configuration values of the given user are stored in global cache
+	 * which is available under the global variable $a->config[$uid].
+	 *
+	 * @param string $uid The user_id
+	 * @param string $cat The category of the configuration value
+	 *
+	 * @return void
+	 */
+	public function load($uid, $cat);
+
+	/**
+	 * @brief Get a particular user's config variable given the category name
+	 * ($family) and a key.
+	 *
+	 * Get a particular user's config value from the given category ($family)
+	 * and the $key from a cached storage in $a->config[$uid].
+	 *
+	 * @param string  $uid           The user_id
+	 * @param string  $cat           The category of the configuration value
+	 * @param string  $k             The configuration key to query
+	 * @param mixed   $default_value optional, The value to return if key is not set (default: null)
+	 * @param boolean $refresh       optional, If true the config is loaded from the db and not from the cache (default: false)
+	 *
+	 * @return mixed Stored value or null if it does not exist
+	 */
+	public function get($uid, $cat, $k, $default_value = null, $refresh = false);
+
+	/**
+	 * @brief Sets a configuration value for a user
+	 *
+	 * Stores a config value ($value) in the category ($family) under the key ($key)
+	 * for the user_id $uid.
+	 *
+	 * @note Please do not store booleans - convert to 0/1 integer values!
+	 *
+	 * @param string $uid   The user_id
+	 * @param string $cat   The category of the configuration value
+	 * @param string $k     The configuration key to set
+	 * @param string $value The value to store
+	 *
+	 * @return mixed Stored $value or false
+	 */
+	public function set($uid, $cat, $k, $value);
+
+	/**
+	 * @brief Deletes the given key from the users's configuration.
+	 *
+	 * Removes the configured value from the stored cache in $a->config[$uid]
+	 * and removes it from the database.
+	 *
+	 * @param string $uid The user_id
+	 * @param string $cat The category of the configuration value
+	 * @param string $k   The configuration key to delete
+	 *
+	 * @return mixed
+	 */
+	public function delete($uid, $cat, $k);
+}
diff --git a/src/Core/Config/JITConfigAdapter.php b/src/Core/Config/JITConfigAdapter.php
new file mode 100644
index 0000000000..1bc3bf5a8f
--- /dev/null
+++ b/src/Core/Config/JITConfigAdapter.php
@@ -0,0 +1,132 @@
+<?php
+namespace Friendica\Core\Config;
+
+use dba;
+use Friendica\BaseObject;
+use Friendica\Database\DBM;
+
+require_once 'include/dba.php';
+
+/**
+ * JustInTime ConfigAdapter
+ *
+ * Default Config Adapter. Provides the best performance for pages loading few configuration variables.
+ *
+ * @author Hypolite Petovan <mrpetovan@gmail.com>
+ */
+class JITConfigAdapter extends BaseObject implements IConfigAdapter
+{
+	private $cache;
+	private $in_db;
+
+	public function load($cat = "config")
+	{
+		// We don't preload "system" anymore.
+		// This reduces the number of database reads a lot.
+		if ($cat === 'system') {
+			return;
+		}
+
+		$a = self::getApp();
+
+		$configs = dba::select('config', ['v', 'k'], ['cat' => $cat]);
+		while ($config = dba::fetch($configs)) {
+			$k = $config['k'];
+			if ($cat === 'config') {
+				$a->config[$k] = $config['v'];
+			} else {
+				$a->config[$cat][$k] = $config['v'];
+				self::$cache[$cat][$k] = $config['v'];
+				self::$in_db[$cat][$k] = true;
+			}
+		}
+		dba::close($configs);
+	}
+
+	public function get($cat, $k, $default_value = null, $refresh = false)
+	{
+		$a = self::getApp();
+
+		if (!$refresh) {
+			// Do we have the cached value? Then return it
+			if (isset($this->cache[$cat][$k])) {
+				if ($this->cache[$cat][$k] === '!<unset>!') {
+					return $default_value;
+				} else {
+					return $this->cache[$cat][$k];
+				}
+			}
+		}
+
+		$config = dba::selectFirst('config', ['v'], ['cat' => $cat, 'k' => $k]);
+		if (DBM::is_result($config)) {
+			// manage array value
+			$value = (preg_match("|^a:[0-9]+:{.*}$|s", $config['v']) ? unserialize($config['v']) : $config['v']);
+
+			// Assign the value from the database to the cache
+			$this->cache[$cat][$k] = $value;
+			$this->in_db[$cat][$k] = true;
+			return $value;
+		} elseif (isset($a->config[$cat][$k])) {
+			// Assign the value (mostly) from the .htconfig.php to the cache
+			$this->cache[$cat][$k] = $a->config[$cat][$k];
+			$this->in_db[$cat][$k] = false;
+
+			return $a->config[$cat][$k];
+		}
+
+		$this->cache[$cat][$k] = '!<unset>!';
+		$this->in_db[$cat][$k] = false;
+
+		return $default_value;
+	}
+
+	public function set($cat, $k, $value)
+	{
+		$a = self::getApp();
+
+		// We store our setting values in a string variable.
+		// So we have to do the conversion here so that the compare below works.
+		// The exception are array values.
+		$dbvalue = (!is_array($value) ? (string)$value : $value);
+
+		$stored = $this->get($cat, $k, null, true);
+
+		if (($stored === $dbvalue) && $this->in_db[$cat][$k]) {
+			return true;
+		}
+
+		if ($cat === 'config') {
+			$a->config[$k] = $dbvalue;
+		} elseif ($cat != 'system') {
+			$a->config[$cat][$k] = $dbvalue;
+		}
+
+		// Assign the just added value to the cache
+		$this->cache[$cat][$k] = $dbvalue;
+
+		// manage array value
+		$dbvalue = (is_array($value) ? serialize($value) : $dbvalue);
+
+		$result = dba::update('config', ['v' => $dbvalue], ['cat' => $cat, 'k' => $k], true);
+
+		if ($result) {
+			$this->in_db[$cat][$k] = true;
+			return $value;
+		}
+
+		return $result;
+	}
+
+	public function delete($cat, $k)
+	{
+		if (isset($this->cache[$cat][$k])) {
+			unset($this->cache[$cat][$k]);
+			unset($this->in_db[$cat][$k]);
+		}
+
+		$result = dba::delete('config', ['cat' => $cat, 'k' => $k]);
+
+		return $result;
+	}
+}
diff --git a/src/Core/Config/JITPConfigAdapter.php b/src/Core/Config/JITPConfigAdapter.php
new file mode 100644
index 0000000000..27f2ed8622
--- /dev/null
+++ b/src/Core/Config/JITPConfigAdapter.php
@@ -0,0 +1,117 @@
+<?php
+namespace Friendica\Core\Config;
+
+use dba;
+use Friendica\BaseObject;
+use Friendica\Database\DBM;
+
+require_once 'include/dba.php';
+
+/**
+ * JustInTime PConfigAdapter
+ *
+ * Default PConfig Adapter. Provides the best performance for pages loading few configuration variables.
+ *
+ * @author Hypolite Petovan <mrpetovan@gmail.com>
+ */
+class JITPConfigAdapter extends BaseObject implements IPConfigAdapter
+{
+	private $in_db;
+
+	public function load($uid, $cat)
+	{
+		$a = self::getApp();
+
+		$pconfigs = dba::select('pconfig', ['v', 'k'], ['cat' => $cat, 'uid' => $uid]);
+		if (DBM::is_result($pconfigs)) {
+			while ($pconfig = dba::fetch($pconfigs)) {
+				$k = $pconfig['k'];
+				$a->config[$uid][$cat][$k] = $pconfig['v'];
+				$this->in_db[$uid][$cat][$k] = true;
+			}
+		} else if ($cat != 'config') {
+			// Negative caching
+			$a->config[$uid][$cat] = "!<unset>!";
+		}
+		dba::close($pconfigs);
+	}
+
+	public function get($uid, $cat, $k, $default_value = null, $refresh = false)
+	{
+		$a = self::getApp();
+
+		if (!$refresh) {
+			// Looking if the whole family isn't set
+			if (isset($a->config[$uid][$cat])) {
+				if ($a->config[$uid][$cat] === '!<unset>!') {
+					return $default_value;
+				}
+			}
+
+			if (isset($a->config[$uid][$cat][$k])) {
+				if ($a->config[$uid][$cat][$k] === '!<unset>!') {
+					return $default_value;
+				}
+				return $a->config[$uid][$cat][$k];
+			}
+		}
+
+		$pconfig = dba::selectFirst('pconfig', ['v'], ['uid' => $uid, 'cat' => $cat, 'k' => $k]);
+		if (DBM::is_result($pconfig)) {
+			$val = (preg_match("|^a:[0-9]+:{.*}$|s", $pconfig['v']) ? unserialize($pconfig['v']) : $pconfig['v']);
+			$a->config[$uid][$cat][$k] = $val;
+			$this->in_db[$uid][$cat][$k] = true;
+
+			return $val;
+		} else {
+			$a->config[$uid][$cat][$k] = '!<unset>!';
+			$this->in_db[$uid][$cat][$k] = false;
+
+			return $default_value;
+		}
+	}
+
+	public function set($uid, $cat, $k, $value)
+	{
+		$a = self::getApp();
+
+		// We store our setting values in a string variable.
+		// So we have to do the conversion here so that the compare below works.
+		// The exception are array values.
+		$dbvalue = (!is_array($value) ? (string)$value : $value);
+
+		$stored = $this->get($uid, $cat, $k, null, true);
+
+		if (($stored === $dbvalue) && $this->in_db[$uid][$cat][$k]) {
+			return true;
+		}
+
+		$a->config[$uid][$cat][$k] = $dbvalue;
+
+		// manage array value
+		$dbvalue = (is_array($value) ? serialize($value) : $dbvalue);
+
+		$result = dba::update('pconfig', ['v' => $dbvalue], ['uid' => $uid, 'cat' => $cat, 'k' => $k], true);
+
+		if ($result) {
+			$this->in_db[$uid][$cat][$k] = true;
+			return $value;
+		}
+
+		return $result;
+	}
+
+	public function delete($uid, $cat, $k)
+	{
+		$a = self::getApp();
+
+		if (!empty($a->config[$uid][$cat][$k])) {
+			unset($a->config[$uid][$cat][$k]);
+			unset($this->in_db[$uid][$cat][$k]);
+		}
+
+		$result = dba::delete('pconfig', ['uid' => $uid, 'cat' => $cat, 'k' => $k]);
+
+		return $result;
+	}
+}
diff --git a/src/Core/Config/PreloadConfigAdapter.php b/src/Core/Config/PreloadConfigAdapter.php
new file mode 100644
index 0000000000..c10f2ee648
--- /dev/null
+++ b/src/Core/Config/PreloadConfigAdapter.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace Friendica\Core\Config;
+
+use dba;
+use Exception;
+use Friendica\BaseObject;
+
+require_once 'include/dba.php';
+
+/**
+ * Preload ConfigAdapter
+ *
+ * Minimize the number of database queries to retrieve configuration values at the cost of memory.
+ *
+ * @author Hypolite Petovan <mrpetovan@gmail.com>
+ */
+class PreloadConfigAdapter extends BaseObject implements IConfigAdapter
+{
+	private $config_loaded = false;
+
+	public function __construct()
+	{
+		$this->load();
+	}
+
+	public function load($family = 'config')
+	{
+		if ($this->config_loaded) {
+			return;
+		}
+
+		$a = self::getApp();
+
+		$configs = dba::select('config', ['cat', 'v', 'k']);
+		while ($config = dba::fetch($configs)) {
+			$cat   = $config['cat'];
+			$k     = $config['k'];
+			$value = (preg_match("|^a:[0-9]+:{.*}$|s", $config['v']) ? unserialize($config['v']) : $config['v']);
+
+			if ($cat === 'config') {
+				$a->config[$k] = $value;
+			} else {
+				$a->config[$cat][$k] = $value;
+			}
+		}
+		dba::close($configs);
+
+		$this->config_loaded = true;
+	}
+
+	public function get($cat, $k, $default_value = null, $refresh = false)
+	{
+		$a = self::getApp();
+
+		$return = $default_value;
+
+		if ($cat === 'config') {
+			if (isset($a->config[$k])) {
+				$return = $a->config[$k];
+			}
+		} else {
+			if (isset($a->config[$cat][$k])) {
+				$return = $a->config[$cat][$k];
+			}
+		}
+
+		return $return;
+	}
+
+	public function set($cat, $k, $value)
+	{
+		$a = self::getApp();
+
+		// We store our setting values as strings.
+		// So we have to do the conversion here so that the compare below works.
+		// The exception are array values.
+		$compare_value = !is_array($value) ? (string)$value : $value;
+
+		if ($this->get($cat, $k) === $compare_value) {
+			return true;
+		}
+
+		if ($cat === 'config') {
+			$a->config[$k] = $value;
+		} else {
+			$a->config[$cat][$k] = $value;
+		}
+
+		// manage array value
+		$dbvalue = is_array($value) ? serialize($value) : $value;
+
+		$result = dba::update('config', ['v' => $dbvalue], ['cat' => $cat, 'k' => $k], true);
+		if (!$result) {
+			throw new Exception('Unable to store config value in [' . $cat . '][' . $k . ']');
+		}
+
+		return true;
+	}
+
+	public function delete($cat, $k)
+	{
+		$a = self::getApp();
+
+		if ($cat === 'config') {
+			if (isset($a->config[$k])) {
+				unset($a->config[$k]);
+			}
+		} else {
+			if (isset($a->config[$cat][$k])) {
+				unset($a->config[$cat][$k]);
+			}
+		}
+
+		$result = dba::delete('config', ['cat' => $cat, 'k' => $k]);
+
+		return $result;
+	}
+}
diff --git a/src/Core/Config/PreloadPConfigAdapter.php b/src/Core/Config/PreloadPConfigAdapter.php
new file mode 100644
index 0000000000..002094a516
--- /dev/null
+++ b/src/Core/Config/PreloadPConfigAdapter.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Friendica\Core\Config;
+
+use dba;
+use Exception;
+use Friendica\BaseObject;
+
+require_once 'include/dba.php';
+
+/**
+ * Preload PConfigAdapter
+ *
+ * Minimize the number of database queries to retrieve configuration values at the cost of memory.
+ *
+ * @author Hypolite Petovan <mrpetovan@gmail.com>
+ */
+class PreloadPConfigAdapter extends BaseObject implements IPConfigAdapter
+{
+	private $config_loaded = false;
+
+	public function __construct($uid)
+	{
+		$this->load($uid, 'config');
+	}
+
+	public function load($uid, $family)
+	{
+		if ($this->config_loaded) {
+			return;
+		}
+
+		$a = self::getApp();
+
+		$pconfigs = dba::select('pconfig', ['cat', 'v', 'k'], ['uid' => $uid]);
+		while ($pconfig = dba::fetch($pconfigs)) {
+			$cat   = $pconfig['cat'];
+			$k     = $pconfig['k'];
+			$value = (preg_match("|^a:[0-9]+:{.*}$|s", $pconfig['v']) ? unserialize($pconfig['v']) : $pconfig['v']);
+
+			$a->config[$uid][$cat][$k] = $value;
+		}
+		dba::close($pconfigs);
+
+		$this->config_loaded = true;
+	}
+
+	public function get($uid, $cat, $k, $default_value = null, $refresh = false)
+	{
+		$a = self::getApp();
+
+		$return = $default_value;
+
+		if (isset($a->config[$uid][$cat][$k])) {
+			$return = $a->config[$uid][$cat][$k];
+		}
+
+		return $return;
+	}
+
+	public function set($uid, $cat, $k, $value)
+	{
+		$a = self::getApp();
+
+		// We store our setting values as strings.
+		// So we have to do the conversion here so that the compare below works.
+		// The exception are array values.
+		$compare_value = !is_array($value) ? (string)$value : $value;
+
+		if ($this->get($uid, $cat, $k) === $compare_value) {
+			return true;
+		}
+
+		$a->config[$uid][$cat][$k] = $value;
+
+		// manage array value
+		$dbvalue = is_array($value) ? serialize($value) : $value;
+
+		$result = dba::update('pconfig', ['v' => $dbvalue], ['uid' => $uid, 'cat' => $cat, 'k' => $k], true);
+
+		if (!$result) {
+			throw new Exception('Unable to store config value in [' . $uid . '][' . $cat . '][' . $k . ']');
+		}
+
+		return true;
+	}
+
+	public function delete($uid, $cat, $k)
+	{
+		$a = self::getApp();
+
+		if (!isset($a->config[$uid][$cat][$k])) {
+			return true;
+		}
+
+		unset($a->config[$uid][$cat][$k]);
+
+		$result = dba::delete('pconfig', ['uid' => $uid, 'cat' => $cat, 'k' => $k]);
+
+		return $result;
+	}
+}