f试ssefahah可能卡卡卡马卡卡计算机视觉事实上事实上飒飒飒gj◐◐◐◐hfkttkgdgkmcuocucocucoc顶顶顶顶顶顶顶顶顶顶顶顶uco朵朵可可放2巍峨2555555666369🌹 �����ѩ�����һ����dddf2qevf2fv69666 ? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!laravel-setting/README.md000064400000012040152665540210011127 0ustar00# Persistent settings package for Laravel [![Downloads](https://poser.pugx.org/akaunting/laravel-setting/d/total.svg)](https://github.com/akaunting/laravel-setting) [![StyleCI](https://styleci.io/repos/101231817/shield?style=flat&branch=master)](https://styleci.io/repos/101231817) [![License](https://poser.pugx.org/akaunting/laravel-setting/license.svg)](LICENSE.md) This package allows you to save settings in a more persistent way. You can use the database and/or json file to save your settings. You can also override the Laravel config. * Driver support * Helper function * Blade directive * Override config values * Encryption * Custom file, table and columns * Auto save * Extra columns * Cache support ## Getting Started ### 1. Install Run the following command: ```bash composer require akaunting/laravel-setting ``` ### 2. Register (for Laravel < 5.5) Register the service provider in `config/app.php` ```php Akaunting\Setting\Provider::class, ``` Add alias if you want to use the facade. ```php 'Setting' => Akaunting\Setting\Facade::class, ``` ### 3. Publish Publish config file. ```bash php artisan vendor:publish --tag=setting ``` ### 4. Database Create table for database driver ```bash php artisan migrate ``` ### 5. Configure You can change the options of your app from `config/setting.php` file ## Usage You can either use the helper method like `setting('foo')` or the facade `Setting::get('foo')` ### Facade ```php Setting::get('foo', 'default'); Setting::get('nested.element'); Setting::set('foo', 'bar'); Setting::forget('foo'); $settings = Setting::all(); ``` ### Helper ```php setting('foo', 'default'); setting('nested.element'); setting(['foo' => 'bar']); setting()->forget('foo'); $settings = setting()->all(); ``` You can call the `save()` method to save the changes. ### Auto Save If you enable the `auto_save` option in the config file, settings will be saved automatically every time the application shuts down if anything has been changed. ### Blade Directive You can get the settings directly in your blade templates using the helper method or the blade directive like `@setting('foo')` ### Override Config Values You can easily override default config values by adding them to the `override` option in `config/setting.php`, thereby eliminating the need to modify the default config files and also allowing you to change said values during production. Ex: ```php 'override' => [ "app.name" => "app_name", "app.env" => "app_env", "mail.driver" => "app_mail_driver", "mail.host" => "app_mail_host", ], ``` The values on the left corresponds to the respective config value (Ex: config('app.name')) and the value on the right is the name of the `key` in your settings table/json file. ### Encryption If you like to encrypt the values for a given key, you can pass the key to the `encrypted_keys` option in `config/setting.php` and the rest is automatically handled by using Laravel's built-in encryption facilities. Ex: ```php 'encrypted_keys' => [ "payment.key", ], ``` ### JSON Storage You can modify the path used on run-time using `setting()->setPath($path)`. ### Database Storage If you want to use the database as settings storage then you should run the `php artisan migrate`. You can modify the table fields from the `create_settings_table` file in the migrations directory. #### Extra Columns If you want to store settings for multiple users/clients in the same database you can do so by specifying extra columns: ```php setting()->setExtraColumns(['user_id' => Auth::user()->id]); ``` where `user_id = x` will now be added to the database query when settings are retrieved, and when new settings are saved, the `user_id` will be populated. If you need more fine-tuned control over which data gets queried, you can use the `setConstraint` method which takes a closure with two arguments: - `$query` is the query builder instance - `$insert` is a boolean telling you whether the query is an insert or not. If it is an insert, you usually don't need to do anything to `$query`. ```php setting()->setConstraint(function($query, $insert) { if ($insert) return; $query->where(/* ... */); }); ``` ### Custom Drivers This package uses the Laravel `Manager` class under the hood, so it's easy to add your own storage driver. All you need to do is extend the abstract `Driver` class, implement the abstract methods and call `setting()->extend`. ```php class MyDriver extends Akaunting\Setting\Contracts\Driver { // ... } app('setting.manager')->extend('mydriver', function($app) { return $app->make('MyDriver'); }); ``` ## Changelog Please see [Releases](../../releases) for more information what has changed recently. ## Contributing Pull requests are more than welcome. You must follow the PSR coding standards. ## Security If you discover any security related issues, please email security@akaunting.com instead of using the issue tracker. ## Credits - [Denis Duliçi](https://github.com/denisdulici) - [All Contributors](../../contributors) ## License The MIT License (MIT). Please see [LICENSE](LICENSE.md) for more information. laravel-setting/.editorconfig000064400000000521152665540210012326 0ustar00; This file is for unifying the coding style for different editors and IDEs. ; More information at https://editorconfig.org root = true [*] charset = utf-8 indent_size = 4 indent_style = space end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false [*.yml] indent_size = 2laravel-setting/tests/unit/JsonDriverTest.php000064400000003334152665540210015435 0ustar00mockFilesystem(); $files->shouldReceive('exists')->once()->with('fakepath')->andReturn(true); $files->shouldReceive('isWritable')->once()->with('fakepath')->andReturn(false); $store = $this->makeStore($files); } /** * @test * @expectedException InvalidArgumentException */ public function throws_exception_when_files_put_fails() { $files = $this->mockFilesystem(); $files->shouldReceive('exists')->once()->with('fakepath')->andReturn(false); $files->shouldReceive('put')->once()->with('fakepath', '{}')->andReturn(false); $store = $this->makeStore($files); } /** * @test * @expectedException RuntimeException */ public function throws_exception_when_file_contains_invalid_json() { $files = $this->mockFilesystem(); $files->shouldReceive('exists')->once()->with('fakepath')->andReturn(true); $files->shouldReceive('isWritable')->once()->with('fakepath')->andReturn(true); $files->shouldReceive('get')->once()->with('fakepath')->andReturn('[[!1!11]'); $store = $this->makeStore($files); $store->get('foo'); } } laravel-setting/tests/unit/DatabaseDriverTest.php000064400000007053152665540210016232 0ustar00mockConnection(); $query = $this->mockQuery($connection); $query->shouldReceive('get')->once()->andReturn([ ['key' => 'nest.one', 'value' => 'old'], ]); $query->shouldReceive('lists')->atMost(1)->andReturn(['nest.one']); $query->shouldReceive('pluck')->atMost(1)->andReturn(['nest.one']); $dbData = $this->getDbData(); unset($dbData[1]); // remove the nest.one array member $query->shouldReceive('where')->with('key', '=', 'nest.one')->andReturn(m::self())->getMock() ->shouldReceive('update')->with(['value' => 'nestone']); $self = $this; // 5.3 compatibility $query->shouldReceive('insert')->once()->andReturnUsing(function ($arg) use ($dbData, $self) { $self->assertEquals(count($dbData), count($arg)); foreach ($dbData as $key => $value) { $self->assertContains($value, $arg); } }); $store = $this->makeStore($connection); $store->set('foo', 'bar'); $store->set('nest.one', 'nestone'); $store->set('nest.two', 'nesttwo'); $store->set('array', ['one', 'two']); $store->save(); } /** @test */ public function extra_columns_are_queried() { $connection = $this->mockConnection(); $query = $this->mockQuery($connection); $query->shouldReceive('where')->once()->with('foo', '=', 'bar') ->andReturn(m::self())->getMock() ->shouldReceive('get')->once()->andReturn([ ['key' => 'foo', 'value' => 'bar'], ]); $store = $this->makeStore($connection); $store->setExtraColumns(['foo' => 'bar']); $this->assertEquals('bar', $store->get('foo')); } /** @test */ public function extra_columns_are_inserted() { $connection = $this->mockConnection(); $query = $this->mockQuery($connection); $query->shouldReceive('where')->times(2)->with('extracol', '=', 'extradata') ->andReturn(m::self()); $query->shouldReceive('get')->once()->andReturn([]); $query->shouldReceive('lists')->atMost(1)->andReturn([]); $query->shouldReceive('pluck')->atMost(1)->andReturn([]); $query->shouldReceive('insert')->once()->with([ ['key' => 'foo', 'value' => 'bar', 'extracol' => 'extradata'], ]); $store = $this->makeStore($connection); $store->setExtraColumns(['extracol' => 'extradata']); $store->set('foo', 'bar'); $store->save(); } protected function getDbData() { return [ ['key' => 'foo', 'value' => 'bar'], ['key' => 'nest.one', 'value' => 'nestone'], ['key' => 'nest.two', 'value' => 'nesttwo'], ['key' => 'array.0', 'value' => 'one'], ['key' => 'array.1', 'value' => 'two'], ]; } protected function mockConnection() { return m::mock('Illuminate\Database\Connection'); } protected function mockQuery($connection) { $query = m::mock('Illuminate\Database\Query\Builder'); $connection->shouldReceive('table')->andReturn($query); return $query; } protected function makeStore($connection) { return new Akaunting\Setting\Drivers\Database($connection); } } laravel-setting/tests/unit/ArrayUtilTest.php000064400000007223152665540210015265 0ustar00assertEquals($expected, Arr::get($data, $key)); } public function getGetData() { return [ [[], 'foo', null], [['foo' => 'bar'], 'foo', 'bar'], [['foo' => 'bar'], 'bar', null], [['foo' => 'bar'], 'foo.bar', null], [['foo' => ['bar' => 'baz']], 'foo.bar', 'baz'], [['foo' => ['bar' => 'baz']], 'foo.baz', null], [['foo' => ['bar' => 'baz']], 'foo', ['bar' => 'baz']], [ ['foo' => 'bar', 'bar' => 'baz'], ['foo', 'bar'], ['foo' => 'bar', 'bar' => 'baz'], ], [ ['foo' => ['bar' => 'baz'], 'bar' => 'baz'], ['foo.bar', 'bar'], ['foo' => ['bar' => 'baz'], 'bar' => 'baz'], ], [ ['foo' => ['bar' => 'baz'], 'bar' => 'baz'], ['foo.bar'], ['foo' => ['bar' => 'baz']], ], [ ['foo' => ['bar' => 'baz'], 'bar' => 'baz'], ['foo.bar', 'baz'], ['foo' => ['bar' => 'baz'], 'baz' => null], ], ]; } /** * @test * @dataProvider getSetData */ public function setSetsCorrectKeyToValue(array $input, $key, $value, array $expected) { Arr::set($input, $key, $value); $this->assertEquals($expected, $input); } public function getSetData() { return [ [ ['foo' => 'bar'], 'foo', 'baz', ['foo' => 'baz'], ], [ [], 'foo', 'bar', ['foo' => 'bar'], ], [ [], 'foo.bar', 'baz', ['foo' => ['bar' => 'baz']], ], [ ['foo' => ['bar' => 'baz']], 'foo.baz', 'foo', ['foo' => ['bar' => 'baz', 'baz' => 'foo']], ], [ ['foo' => ['bar' => 'baz']], 'foo.baz.bar', 'baz', ['foo' => ['bar' => 'baz', 'baz' => ['bar' => 'baz']]], ], [ [], 'foo.bar.baz', 'foo', ['foo' => ['bar' => ['baz' => 'foo']]], ], ]; } /** @test */ public function setThrowsExceptionOnNonArraySegment() { $data = ['foo' => 'bar']; $this->setExpectedException('UnexpectedValueException', 'Non-array segment encountered'); Arr::set($data, 'foo.bar', 'baz'); } /** * @test * @dataProvider getHasData */ public function hasReturnsCorrectly(array $input, $key, $expected) { $this->assertEquals($expected, Arr::has($input, $key)); } public function getHasData() { return [ [[], 'foo', false], [['foo' => 'bar'], 'foo', true], [['foo' => 'bar'], 'bar', false], [['foo' => 'bar'], 'foo.bar', false], [['foo' => ['bar' => 'baz']], 'foo.bar', true], [['foo' => ['bar' => 'baz']], 'foo.baz', false], [['foo' => ['bar' => 'baz']], 'foo', true], [['foo' => null], 'foo', true], [['foo' => ['bar' => null]], 'foo.bar', true], ]; } } laravel-setting/tests/unit/HelperTest.php000064400000002242152665540210014564 0ustar00bind('setting', function () use ($store) { return $store; }); } /** @test */ public function helper_without_parameters_returns_store() { $this->assertInstanceOf('Akaunting\Setting\Contracts\Driver', setting()); } /** @test */ public function single_parameter_get_a_key_from_store() { app('setting')->shouldReceive('get')->with('foo', null)->once(); setting('foo'); } public function two_parameters_return_a_default_value() { app('setting')->shouldReceive('get')->with('foo', 'bar')->once(); setting('foo', 'bar'); } /** @test */ public function array_parameter_call_set_method_into_store() { app('setting')->shouldReceive('set')->with(['foo', 'bar'])->once(); setting(['foo', 'bar']); } } laravel-setting/tests/functional/AbstractFunctionalTest.php000064400000006323152665540210020322 0ustar00assertEquals($expected, $store->all(), $message); $store->save(); $store = $this->createStore(); $this->assertEquals($expected, $store->all(), $message); } protected function assertStoreKeyEquals($store, $key, $expected, $message = null) { $this->assertEquals($expected, $store->get($key), $message); $store->save(); $store = $this->createStore(); $this->assertEquals($expected, $store->get($key), $message); } /** @test */ public function store_is_initially_empty() { $store = $this->createStore(); $this->assertEquals([], $store->all()); } /** @test */ public function written_changes_are_saved() { $store = $this->createStore(); $store->set('foo', 'bar'); $this->assertStoreKeyEquals($store, 'foo', 'bar'); } /** @test */ public function nested_keys_are_nested() { $store = $this->createStore(); $store->set('foo.bar', 'baz'); $this->assertStoreEquals($store, ['foo' => ['bar' => 'baz']]); } /** @test */ public function cannot_set_nested_key_on_non_array_member() { $store = $this->createStore(); $store->set('foo', 'bar'); $this->setExpectedException('UnexpectedValueException', 'Non-array segment encountered'); $store->set('foo.bar', 'baz'); } /** @test */ public function can_forget_key() { $store = $this->createStore(); $store->set('foo', 'bar'); $store->set('bar', 'baz'); $this->assertStoreEquals($store, ['foo' => 'bar', 'bar' => 'baz']); $store->forget('foo'); $this->assertStoreEquals($store, ['bar' => 'baz']); } /** @test */ public function can_forget_nested_key() { $store = $this->createStore(); $store->set('foo.bar', 'baz'); $store->set('foo.baz', 'bar'); $store->set('bar.foo', 'baz'); $this->assertStoreEquals($store, [ 'foo' => [ 'bar' => 'baz', 'baz' => 'bar', ], 'bar' => [ 'foo' => 'baz', ], ]); $store->forget('foo.bar'); $this->assertStoreEquals($store, [ 'foo' => [ 'baz' => 'bar', ], 'bar' => [ 'foo' => 'baz', ], ]); $store->forget('bar.foo'); $expected = [ 'foo' => [ 'baz' => 'bar', ], 'bar' => [ ], ]; if ($store instanceof Database) { unset($expected['bar']); } $this->assertStoreEquals($store, $expected); } /** @test */ public function can_forget_all() { $store = $this->createStore(['foo' => 'bar']); $this->assertStoreEquals($store, ['foo' => 'bar']); $store->forgetAll(); $this->assertStoreEquals($store, []); } } laravel-setting/tests/functional/MemoryTest.php000064400000001134152665540210015777 0ustar00assertEquals($expected, $store->all(), $message); // removed persistance test assertions } protected function assertStoreKeyEquals($store, $key, $expected, $message = null) { $this->assertEquals($expected, $store->get($key), $message); // removed persistance test assertions } protected function createStore(array $data = null) { return new \Akaunting\Setting\Drivers\Memory($data); } } laravel-setting/tests/functional/DatabaseTest.php000064400000002211152665540210016230 0ustar00container = new \Illuminate\Container\Container(); $this->capsule = new \Illuminate\Database\Capsule\Manager($this->container); $this->capsule->setAsGlobal(); $this->container['db'] = $this->capsule; $this->capsule->addConnection([ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ]); $this->capsule->schema()->create('settings', function ($t) { $t->string('key', 64)->unique(); $t->string('value', 4096); }); } public function tearDown() { $this->capsule->schema()->drop('settings'); unset($this->capsule); unset($this->container); } protected function createStore(array $data = []) { if ($data) { $store = $this->createStore(); $store->set($data); $store->save(); unset($store); } return new \Akaunting\Setting\Drivers\Database( $this->capsule->getConnection() ); } } laravel-setting/tests/functional/JsonTest.php000064400000001217152665540210015442 0ustar00 ./tests/ laravel-setting/.styleci.yml000064400000000054152665540210012127 0ustar00preset: psr2 enabled: - concat_with_spaceslaravel-setting/src/Manager.php000064400000002747152665540210012537 0ustar00container = $app ?? app(); parent::__construct($this->container); } public function getDefaultDriver() { return config('setting.driver'); } public function createJsonDriver() { $path = config('setting.json.path'); return new Json($this->container['files'], $path); } public function createDatabaseDriver() { $connection = $this->container['db']->connection(config('setting.database.connection')); $table = config('setting.database.table'); $key = config('setting.database.key'); $value = config('setting.database.value'); $encryptedKeys = config('setting.encrypted_keys'); return new Database($connection, $table, $key, $value, $encryptedKeys); } public function createMemoryDriver() { return new Memory(); } public function createArrayDriver() { return $this->createMemoryDriver(); } } laravel-setting/src/Migrations/2017_08_24_000000_create_settings_table.php000064400000001607152665540210021711 0ustar00table = config('setting.database.table'); $this->key = config('setting.database.key'); $this->value = config('setting.database.value'); } /** * Run the migrations. * * @return void */ public function up() { Schema::create($this->table, function (Blueprint $table) { $table->increments('id'); $table->string($this->key)->index(); $table->text($this->value); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop($this->table); } } laravel-setting/src/Middleware/AutoSaveSetting.php000064400000001062152665540210016314 0ustar00setting = app('setting'); } /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * * @return mixed */ public function handle($request, Closure $next) { $response = $next($request); $this->setting->save(); return $response; } } laravel-setting/src/Provider.php000064400000004151152665540210012746 0ustar00publishes([ __DIR__ . '/Config/setting.php' => config_path('setting.php'), __DIR__ . '/Migrations/2017_08_24_000000_create_settings_table.php' => database_path('migrations/2017_08_24_000000_create_settings_table.php'), ], 'setting'); // Auto save setting if (config('setting.auto_save')) { $kernel = $this->app['Illuminate\Contracts\Http\Kernel']; $kernel->pushMiddleware(AutoSaveSetting::class); } $this->override(); // Register blade directive $this->callAfterResolving('blade.compiler', function (BladeCompiler $compiler) { $compiler->directive('setting', function ($expression) { return ""; }); }); } /** * Register the application services. * * @return void */ public function register() { $this->app->singleton('setting.manager', function ($app) { return new Manager($app); }); $this->app->singleton('setting', function ($app) { return $app['setting.manager']->driver(); }); $this->mergeConfigFrom(__DIR__ . '/Config/setting.php', 'setting'); } private function override() { $override = config('setting.override', []); foreach (Arr::dot($override) as $config_key => $setting_key) { $config_key = is_string($config_key) ? $config_key : $setting_key; try { if (! is_null($value = setting($setting_key))) { config([$config_key => $value]); } } catch (\Exception $e) { continue; } } } } laravel-setting/src/Support/Arr.php000064400000007730152665540210013362 0ustar00 $value) { if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) { $merged[$key] = static::merge($merged[$key], $value); } elseif (is_numeric($key)) { if (!in_array($value, $merged)) { $merged[] = $value; } } else { $merged[$key] = $value; } } return $merged; } } laravel-setting/src/helpers.php000064400000001662152665540210012622 0ustar00set($key); return $setting; } return $setting->get($key, $default); } } laravel-setting/src/Contracts/Driver.php000064400000014713152665540210014354 0ustar00with_fallback = false; return $this; } /** * Get a specific key from the settings data. * * @param string|array $key * @param mixed $default Optional default value. * * @return mixed */ public function get($key, $default = null) { if (!$this->checkExtraColumns()) { return false; } $this->load(); return Arr::get($this->data, $key, $default); } /** * Get the fallback value if default is null. * * @param string|array $key * @param mixed $default * * @return mixed */ public function getFallback($key, $default = null) { if (($default !== null) || is_array($key)) { return $default; } return Arr::get((array) config('setting.fallback'), $key); } /** * Check if the given value is same as fallback. * * @param string $key * @param string $value * * @return bool */ public function isEqualToFallback($key, $value) { return (string) $this->getFallback($key) == (string) $value; } /** * Determine if a key exists in the settings data. * * @param string $key * * @return bool */ public function has($key) { if (!$this->checkExtraColumns()) { return false; } $this->load(); return Arr::has($this->data, $key); } /** * Set a specific key to a value in the settings data. * * @param string|array $key Key string or associative array of key => value * @param mixed $value Optional only if the first argument is an array */ public function set($key, $value = null) { if (!$this->checkExtraColumns()) { return; } $this->load(); $this->unsaved = true; if (is_array($key)) { foreach ($key as $k => $v) { Arr::set($this->data, $k, $v); } } else { Arr::set($this->data, $key, $value); } } /** * Unset a key in the settings data. * * @param string $key */ public function forget($key) { if (!$this->checkExtraColumns()) { return; } $this->unsaved = true; if ($this->has($key)) { Arr::forget($this->data, $key); } } /** * Unset all keys in the settings data. * * @return void */ public function forgetAll() { if (!$this->checkExtraColumns()) { return; } if (config('setting.cache.enabled')) { Cache::forget($this->getCacheKey()); } $this->unsaved = true; $this->data = []; } /** * Get all settings data. * * @return array|bool */ public function all() { if (!$this->checkExtraColumns()) { return []; } $this->load(); return $this->data; } /** * Save any changes done to the settings data. * * @return void */ public function save() { if (!$this->checkExtraColumns()) { return; } if (!$this->unsaved) { // either nothing has been changed, or data has not been loaded, so // do nothing by returning early return; } if (config('setting.cache.enabled') && config('setting.cache.auto_clear')) { Cache::forget($this->getCacheKey()); } $this->write($this->data); $this->unsaved = false; } /** * Make sure data is loaded. * * @param $force Force a reload of data. Default false. */ public function load($force = false) { if (!$this->checkExtraColumns()) { return; } if ($this->loaded && !$force) { return; } $fallback_data = $this->with_fallback ? config('setting.fallback') : []; $driver_data = $this->readData(); $this->data = Arr::merge((array) $fallback_data, (array) $driver_data); $this->loaded = true; } /** * Read data from driver or cache * * @return array */ public function readData() { if (config('setting.cache.enabled')) { return $this->readDataFromCache(); } return $this->read(); } /** * Read data from cache * * @return array */ public function readDataFromCache() { return Cache::remember($this->getCacheKey(), config('setting.cache.ttl'), function () { return $this->read(); }); } /** * Check if extra columns are set up. * * @return boolean */ public function checkExtraColumns() { if (!$required_extra_columns = config('setting.required_extra_columns')) { return true; } if (array_keys_exists($required_extra_columns, $this->getExtraColumns())) { return true; } return false; } /** * Get cache key based on extra columns. * * @return string */ public function getCacheKey() { $key = config('setting.cache.key'); foreach ($this->getExtraColumns() as $name => $value) { $key .= '_' . $name . '_' . $value; } return $key; } /** * Get extra columns added to the rows. * * @return array */ abstract protected function getExtraColumns(); /** * Read data from driver. * * @return array */ abstract protected function read(); /** * Write data to driver. * * @param array $data * * @return void */ abstract protected function write(array $data); } laravel-setting/src/Config/setting.php000064400000006631152665540210014043 0ustar00 false, /* |-------------------------------------------------------------------------- | Cache |-------------------------------------------------------------------------- | | Options for caching. Set whether to enable cache, its key, time to live | in seconds and whether to auto clear after save. | */ 'cache' => [ 'enabled' => false, 'key' => 'setting', 'ttl' => 3600, 'auto_clear' => true, ], /* |-------------------------------------------------------------------------- | Setting driver |-------------------------------------------------------------------------- | | Select where to store the settings. | | Supported: "database", "json", "memory" | */ 'driver' => 'database', /* |-------------------------------------------------------------------------- | Database driver |-------------------------------------------------------------------------- | | Options for database driver. Enter which connection to use, null means | the default connection. Set the table and column names. | */ 'database' => [ 'connection' => null, 'table' => 'settings', 'key' => 'key', 'value' => 'value', ], /* |-------------------------------------------------------------------------- | JSON driver |-------------------------------------------------------------------------- | | Options for json driver. Enter the full path to the .json file. | */ 'json' => [ 'path' => storage_path() . '/settings.json', ], /* |-------------------------------------------------------------------------- | Override application config values |-------------------------------------------------------------------------- | | If defined, settings package will override these config values. | | Sample: | "app.locale" => "settings.locale", | */ 'override' => [ ], /* |-------------------------------------------------------------------------- | Fallback |-------------------------------------------------------------------------- | | Define fallback settings to be used in case the default is null | | Sample: | "currency" => "USD", | */ 'fallback' => [ ], /* |-------------------------------------------------------------------------- | Required Extra Columns |-------------------------------------------------------------------------- | | The list of columns required to be set up | | Sample: | "user_id", | "tenant_id", | */ 'required_extra_columns' => [ ], /* |-------------------------------------------------------------------------- | Encryption |-------------------------------------------------------------------------- | | Define the keys which should be crypt automatically. | | Sample: | "payment.key" | */ 'encrypted_keys' => [ ], ]; laravel-setting/src/Facade.php000064400000000425152665540210012317 0ustar00data = $data; } } /** * {@inheritdoc} */ protected function getExtraColumns() { return []; } /** * {@inheritdoc} */ protected function read() { return $this->data; } /** * {@inheritdoc} */ protected function write(array $data) { // do nothing } } laravel-setting/src/Drivers/Json.php000064400000003347152665540210013511 0ustar00files = $files; $this->setPath($path ?: storage_path() . '/settings.json'); } /** * Set the path for the JSON file. * * @param string $path */ public function setPath($path) { // If the file does not already exist, we will attempt to create it. if (!$this->files->exists($path)) { $result = $this->files->put($path, '{}'); if ($result === false) { throw new \InvalidArgumentException("Could not write to $path."); } } if (!$this->files->isWritable($path)) { throw new \InvalidArgumentException("$path is not writable."); } $this->path = $path; } /** * {@inheritdoc} */ protected function getExtraColumns() { return []; } /** * {@inheritdoc} */ protected function read() { $contents = $this->files->get($this->path); $data = json_decode($contents, true); if ($data === null) { throw new \RuntimeException("Invalid JSON in {$this->path}"); } return $data; } /** * {@inheritdoc} */ protected function write(array $data) { if ($data) { $contents = json_encode($data); } else { $contents = '{}'; } $this->files->put($this->path, $contents); } } laravel-setting/src/Drivers/Database.php000064400000022643152665540210014304 0ustar00connection = $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; } } laravel-setting/composer.json000064400000002126152665540210012376 0ustar00{ "name": "akaunting/laravel-setting", "description": "Persistent settings package for Laravel", "keywords": [ "laravel", "persistent", "settings", "config" ], "license": "MIT", "authors": [ { "name": "Denis Duliçi", "email": "info@akaunting.com", "homepage": "https://akaunting.com", "role": "Developer" } ], "require": { "php": ">=5.5.9", "laravel/framework": ">=5.3" }, "require-dev": { "phpunit/phpunit": ">=4.8", "mockery/mockery": "0.9.*", "laravel/framework": ">=5.3" }, "autoload": { "psr-4": { "Akaunting\\Setting\\": "./src" }, "files": [ "src/helpers.php" ] }, "extra": { "laravel": { "providers": [ "Akaunting\\Setting\\Provider" ], "aliases": { "Setting": "Akaunting\\Setting\\Facade" } } }, "minimum-stability": "dev", "prefer-stable": true } laravel-setting/.gitignore000064400000000157152665540210011646 0ustar00/.idea /.history /.vscode /tests/databases /vendor .DS_Store .phpunit.result.cache composer.phar composer.lock laravel-setting/.gitattributes000064400000001116152665540210012545 0ustar00# Set the default behavior, in case people don't have core.autocrlf set. * text eol=lf # Explicitly declare text files you want to always be normalized and converted # to native line endings on checkout. *.c text *.h text # Declare files that will always have CRLF line endings on checkout. *.sln text eol=crlf # Denote all files that are truly binary and should not be modified. *.png binary *.jpg binary *.otf binary *.eot binary *.svg binary *.ttf binary *.woff binary *.woff2 binary *.css linguist-vendored *.scss linguist-vendored *.js linguist-vendored CHANGELOG.md export-ignore laravel-setting/LICENSE.md000064400000002126152665540210011260 0ustar00The MIT License (MIT) Copyright (c) 2015 Andreas Lutro Copyright (c) 2017 Akaunting Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. laravel-setting/.github/workflows/tests.yml000064400000001612152665540210015135 0ustar00name: Tests on: [push, pull_request] jobs: tests: name: PHP ${{ matrix.php }} runs-on: ubuntu-latest strategy: matrix: php: ['7.3', '7.4', '8.0', '8.1'] steps: - name: Checkout code uses: actions/checkout@v2 - name: Cache composer uses: actions/cache@v1 with: path: ~/.composer/cache/files key: php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extension-csv: bcmath, ctype, dom, fileinfo, intl, gd, json, mbstring, pdo, pdo_sqlite, openssl, sqlite, xml, zip coverage: none - name: Install composer run: composer install --no-interaction --no-scripts --no-suggest --prefer-source - name: Execute tests run: vendor/bin/phpunit