373 lines
9.4 KiB
PHP
373 lines
9.4 KiB
PHP
<?php
|
|
|
|
namespace Akaunting\Setting\Drivers;
|
|
|
|
use Akaunting\Setting\Contracts\Driver;
|
|
use Akaunting\Setting\Support\Arr;
|
|
use Closure;
|
|
use Illuminate\Database\Connection;
|
|
use Illuminate\Support\Arr as LaravelArr;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
|
|
class Database extends Driver
|
|
{
|
|
/**
|
|
* The database connection instance.
|
|
*
|
|
* @var \Illuminate\Database\Connection
|
|
*/
|
|
protected $connection;
|
|
|
|
/**
|
|
* The table to query from.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $table;
|
|
|
|
/**
|
|
* The key column name to query from.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $key;
|
|
|
|
/**
|
|
* The value column name to query from.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $value;
|
|
|
|
/**
|
|
* Keys which should be encrypt automatically.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $encrypted_keys;
|
|
|
|
/**
|
|
* Any query constraints that should be applied.
|
|
*
|
|
* @var Closure|null
|
|
*/
|
|
protected $query_constraint;
|
|
|
|
/**
|
|
* Any extra columns that should be added to the rows.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $extra_columns = [];
|
|
|
|
/**
|
|
* @param \Illuminate\Database\Connection $connection
|
|
* @param string $table
|
|
*/
|
|
public function __construct(Connection $connection, $table = null, $key = null, $value = null, $encrypted_keys = [])
|
|
{
|
|
$this->connection = $connection;
|
|
$this->table = $table ?: 'settings';
|
|
$this->key = $key ?: 'key';
|
|
$this->value = $value ?: 'value';
|
|
$this->encrypted_keys = $encrypted_keys ?: [];
|
|
}
|
|
|
|
/**
|
|
* Set the table to query from.
|
|
*
|
|
* @param string $table
|
|
*/
|
|
public function setTable($table)
|
|
{
|
|
$this->table = $table;
|
|
}
|
|
|
|
/**
|
|
* Set the key column name to query from.
|
|
*
|
|
* @param string $key
|
|
*/
|
|
public function setKey($key)
|
|
{
|
|
$this->key = $key;
|
|
}
|
|
|
|
/**
|
|
* Set the value column name to query from.
|
|
*
|
|
* @param string $value
|
|
*/
|
|
public function setValue($value)
|
|
{
|
|
$this->value = $value;
|
|
}
|
|
|
|
/**
|
|
* Set the query constraint.
|
|
*
|
|
* @param Closure $callback
|
|
*/
|
|
public function setConstraint(Closure $callback)
|
|
{
|
|
$this->data = [];
|
|
$this->loaded = false;
|
|
$this->query_constraint = $callback;
|
|
}
|
|
|
|
/**
|
|
* Set extra columns to be added to the rows.
|
|
*
|
|
* @param array $columns
|
|
*/
|
|
public function setExtraColumns(array $columns)
|
|
{
|
|
$this->extra_columns = $columns;
|
|
}
|
|
|
|
/**
|
|
* Get extra columns added to the rows.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getExtraColumns()
|
|
{
|
|
return $this->extra_columns;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function forget($key)
|
|
{
|
|
parent::forget($key);
|
|
|
|
// because the database driver cannot store empty arrays, remove empty
|
|
// arrays to keep data consistent before and after saving
|
|
$segments = explode('.', $key);
|
|
array_pop($segments);
|
|
|
|
while ($segments) {
|
|
$segment = implode('.', $segments);
|
|
|
|
// non-empty array - exit out of the loop
|
|
if ($this->get($segment)) {
|
|
break;
|
|
}
|
|
|
|
// remove the empty array and move on to the next segment
|
|
$this->forget($segment);
|
|
array_pop($segments);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
protected function write(array $data)
|
|
{
|
|
// Get current data
|
|
$db_data = $this->newQuery()->get([$this->key, $this->value])->toArray();
|
|
|
|
$insert_data = LaravelArr::dot($data);
|
|
$update_data = [];
|
|
$delete_keys = [];
|
|
|
|
foreach ($db_data as $db_row) {
|
|
$key = $db_row->{$this->key};
|
|
$value = $db_row->{$this->value};
|
|
|
|
$is_in_insert = $is_different_in_db = $is_same_as_fallback = false;
|
|
|
|
if (isset($insert_data[$key])) {
|
|
$is_in_insert = true;
|
|
$is_different_in_db = (string) $insert_data[$key] != (string) $value;
|
|
$is_same_as_fallback = $this->isEqualToFallback($key, $insert_data[$key]);
|
|
}
|
|
|
|
if ($is_in_insert) {
|
|
if ($is_same_as_fallback) {
|
|
// Delete if new data is same as fallback
|
|
$delete_keys[] = $key;
|
|
} elseif ($is_different_in_db) {
|
|
// Update if new data is different from db
|
|
$update_data[$key] = $insert_data[$key];
|
|
}
|
|
} else {
|
|
// Delete if current db not available in new data
|
|
$delete_keys[] = $key;
|
|
}
|
|
|
|
unset($insert_data[$key]);
|
|
}
|
|
|
|
foreach ($update_data as $key => $value) {
|
|
$value = $this->prepareValue($key, $value);
|
|
|
|
$this->newQuery()
|
|
->where($this->key, '=', $key)
|
|
->update([$this->value => $value]);
|
|
}
|
|
|
|
if ($insert_data) {
|
|
$this->newQuery(true)
|
|
->insert($this->prepareInsertData($insert_data));
|
|
}
|
|
|
|
if ($delete_keys) {
|
|
$this->newQuery()
|
|
->whereIn($this->key, $delete_keys)
|
|
->delete();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transforms settings data into an array ready to be insterted into the
|
|
* database. Call array_dot on a multidimensional array before passing it
|
|
* into this method!
|
|
*
|
|
* @param array $data Call array_dot on a multidimensional array before passing it into this method!
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function prepareInsertData(array $data)
|
|
{
|
|
$db_data = [];
|
|
|
|
if ($this->getExtraColumns()) {
|
|
foreach ($data as $key => $value) {
|
|
$value = $this->prepareValue($key, $value);
|
|
|
|
// Don't insert if same as fallback
|
|
if ($this->isEqualToFallback($key, $value)) {
|
|
continue;
|
|
}
|
|
|
|
$db_data[] = array_merge(
|
|
$this->getExtraColumns(),
|
|
[$this->key => $key, $this->value => $value]
|
|
);
|
|
}
|
|
} else {
|
|
foreach ($data as $key => $value) {
|
|
$value = $this->prepareValue($key, $value);
|
|
|
|
// Don't insert if same as fallback
|
|
if ($this->isEqualToFallback($key, $value)) {
|
|
continue;
|
|
}
|
|
|
|
$db_data[] = [$this->key => $key, $this->value => $value];
|
|
}
|
|
}
|
|
|
|
return $db_data;
|
|
}
|
|
|
|
/**
|
|
* Checks if the provided key should be encrypted or not.
|
|
* Also type casts the given value to a string so errors with booleans or integers are handeled.
|
|
* Otherwise it returns the original value.
|
|
*
|
|
* @param string $key Key to check if it's inside the encryptedValues variable.
|
|
* @param mixed $value Info: Encryption only supports strings.
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function prepareValue(string $key, $value)
|
|
{
|
|
// Check if key should be encrypted
|
|
if (in_array($key, $this->encrypted_keys)) {
|
|
// Cast to string to avoid error when a user passes a boolean value
|
|
return Crypt::encryptString((string) $value);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Checks if the provided key should be decrypted or not.
|
|
* Otherwise it returns the original value.
|
|
*
|
|
* @param string $key Key to check if it's inside the encryptedValues variable.
|
|
* @param mixed $value Info: Encryption only supports strings.
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function unpackValue(string $key, $value)
|
|
{
|
|
// Check if key should be encrypted
|
|
if (in_array($key, $this->encrypted_keys)) {
|
|
// Cast to string to avoid error when a user passes a boolean value
|
|
return Crypt::decryptString((string) $value);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
protected function read()
|
|
{
|
|
return $this->parseReadData($this->newQuery()->get());
|
|
}
|
|
|
|
/**
|
|
* Parse data coming from the database.
|
|
*
|
|
* @param array $data
|
|
*
|
|
* @return array
|
|
*/
|
|
public function parseReadData($data)
|
|
{
|
|
$results = [];
|
|
|
|
foreach ($data as $row) {
|
|
if (is_array($row)) {
|
|
$key = $row[$this->key];
|
|
$value = $row[$this->value];
|
|
} elseif (is_object($row)) {
|
|
$key = $row->{$this->key};
|
|
$value = $row->{$this->value};
|
|
} else {
|
|
$msg = 'Expected array or object, got ' . gettype($row);
|
|
throw new \UnexpectedValueException($msg);
|
|
}
|
|
|
|
// Encryption
|
|
$value = $this->unpackValue($key, $value);
|
|
|
|
Arr::set($results, $key, $value);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Create a new query builder instance.
|
|
*
|
|
* @param bool $insert
|
|
*
|
|
* @return \Illuminate\Database\Query\Builder
|
|
*/
|
|
protected function newQuery($insert = false)
|
|
{
|
|
$query = $this->connection->table($this->table);
|
|
|
|
if (!$insert) {
|
|
foreach ($this->getExtraColumns() as $key => $value) {
|
|
$query->where($key, '=', $value);
|
|
}
|
|
}
|
|
|
|
if ($this->query_constraint !== null) {
|
|
$callback = $this->query_constraint;
|
|
$callback($query, $insert);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
}
|