[
  {
    "path": ".editorconfig",
    "content": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at https://editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nindent_size = 4\nindent_style = space\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[*.yml]\nindent_size = 2"
  },
  {
    "path": ".gitattributes",
    "content": "# Set the default behavior, in case people don't have core.autocrlf set.\n* text eol=lf\n\n# Explicitly declare text files you want to always be normalized and converted\n# to native line endings on checkout.\n*.c text\n*.h text\n\n# Declare files that will always have CRLF line endings on checkout.\n*.sln text eol=crlf\n\n# Denote all files that are truly binary and should not be modified.\n*.png binary\n*.jpg binary\n*.otf binary\n*.eot binary\n*.svg binary\n*.ttf binary\n*.woff binary\n*.woff2 binary\n\n*.css linguist-vendored\n*.scss linguist-vendored\n*.js linguist-vendored\nCHANGELOG.md export-ignore\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\n\non: [push, pull_request]\n\njobs:\n  tests:\n\n    name: PHP ${{ matrix.php }}\n\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        php: ['7.3', '7.4', '8.0', '8.1']\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v2\n\n      - name: Cache composer\n        uses: actions/cache@v1\n        with:\n          path: ~/.composer/cache/files\n          key: php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php }}\n          extension-csv: bcmath, ctype, dom, fileinfo, intl, gd, json, mbstring, pdo, pdo_sqlite, openssl, sqlite, xml, zip\n          coverage: none\n\n      - name: Install composer\n        run: composer install --no-interaction --no-scripts --no-suggest --prefer-source\n\n      - name: Execute tests\n        run: vendor/bin/phpunit\n"
  },
  {
    "path": ".gitignore",
    "content": "/.idea\n/.history\n/.vscode\n/tests/databases\n/vendor\n.DS_Store\n.phpunit.result.cache\ncomposer.phar\ncomposer.lock\n"
  },
  {
    "path": ".styleci.yml",
    "content": "preset: psr2\n\nenabled:\n - concat_with_spaces"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Andreas Lutro\n\nCopyright (c) 2017 Akaunting\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Persistent settings package for Laravel\n\n[![Downloads](https://poser.pugx.org/akaunting/laravel-setting/d/total.svg)](https://github.com/akaunting/laravel-setting)\n[![StyleCI](https://styleci.io/repos/101231817/shield?style=flat&branch=master)](https://styleci.io/repos/101231817)\n[![License](https://poser.pugx.org/akaunting/laravel-setting/license.svg)](LICENSE.md)\n\nThis 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.\n\n* Driver support\n* Helper function\n* Blade directive\n* Override config values\n* Encryption\n* Custom file, table and columns\n* Auto save\n* Extra columns\n* Cache support\n\n## Getting Started\n\n### 1. Install\n\nRun the following command:\n\n```bash\ncomposer require akaunting/laravel-setting\n```\n\n### 2. Register (for Laravel < 5.5)\n\nRegister the service provider in `config/app.php`\n\n```php\nAkaunting\\Setting\\Provider::class,\n```\n\nAdd alias if you want to use the facade.\n\n```php\n'Setting' => Akaunting\\Setting\\Facade::class,\n```\n\n### 3. Publish\n\nPublish config file.\n\n```bash\nphp artisan vendor:publish --tag=setting\n```\n\n### 4. Database\n\nCreate table for database driver\n\n```bash\nphp artisan migrate\n```\n\n### 5. Configure\n\nYou can change the options of your app from `config/setting.php` file\n\n## Usage\n\nYou can either use the helper method like `setting('foo')` or the facade `Setting::get('foo')`\n\n### Facade\n\n```php\nSetting::get('foo', 'default');\nSetting::get('nested.element');\nSetting::set('foo', 'bar');\nSetting::forget('foo');\n$settings = Setting::all();\n```\n\n### Helper\n\n```php\nsetting('foo', 'default');\nsetting('nested.element');\nsetting(['foo' => 'bar']);\nsetting()->forget('foo');\n$settings = setting()->all();\n```\n\nYou can call the  `save()` method to save the changes.\n\n### Auto Save\n\nIf 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.\n\n### Blade Directive\n\nYou can get the settings directly in your blade templates using the helper method or the blade directive like `@setting('foo')`\n\n### Override Config Values\n\nYou 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:\n\n```php\n'override' => [\n    \"app.name\" => \"app_name\",\n    \"app.env\" => \"app_env\",\n    \"mail.driver\" => \"app_mail_driver\",\n    \"mail.host\" => \"app_mail_host\",\n],\n```\n\nThe 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.\n\n### Encryption\n\nIf 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:\n\n```php\n'encrypted_keys' => [\n    \"payment.key\",\n],\n```\n\n### JSON Storage\n\nYou can modify the path used on run-time using `setting()->setPath($path)`.\n\n### Database Storage\n\nIf 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.\n\n#### Extra Columns\n\nIf you want to store settings for multiple users/clients in the same database you can do so by specifying extra columns:\n\n```php\nsetting()->setExtraColumns(['user_id' => Auth::user()->id]);\n```\n\nwhere `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.\n\nIf you need more fine-tuned control over which data gets queried, you can use the `setConstraint` method which takes a closure with two arguments:\n\n- `$query` is the query builder instance\n- `$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`.\n\n```php\nsetting()->setConstraint(function($query, $insert) {\n\tif ($insert) return;\n\t$query->where(/* ... */);\n});\n```\n\n### Custom Drivers\n\nThis 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`.\n\n```php\nclass MyDriver extends Akaunting\\Setting\\Contracts\\Driver\n{\n\t// ...\n}\n\napp('setting.manager')->extend('mydriver', function($app) {\n\treturn $app->make('MyDriver');\n});\n```\n\n## Changelog\n\nPlease see [Releases](../../releases) for more information what has changed recently.\n\n## Contributing\n\nPull requests are more than welcome. You must follow the PSR coding standards.\n\n## Security\n\nIf you discover any security related issues, please email security@akaunting.com instead of using the issue tracker.\n\n## Credits\n\n- [Denis Duliçi](https://github.com/denisdulici)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [LICENSE](LICENSE.md) for more information.\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"akaunting/laravel-setting\",\n    \"description\": \"Persistent settings package for Laravel\",\n    \"keywords\": [\n        \"laravel\",\n        \"persistent\",\n        \"settings\",\n        \"config\"\n    ],\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Denis Duliçi\",\n            \"email\": \"info@akaunting.com\",\n            \"homepage\": \"https://akaunting.com\",\n            \"role\": \"Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=5.5.9\",\n        \"laravel/framework\": \">=5.3\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \">=4.8\",\n        \"mockery/mockery\": \"0.9.*\",\n        \"laravel/framework\": \">=5.3\"\n    },\n    \"autoload\": {\n    \"psr-4\": {\n            \"Akaunting\\\\Setting\\\\\": \"./src\"\n        },\n        \"files\": [\n            \"src/helpers.php\"\n        ]\n    },\n    \"extra\": {\n        \"laravel\": {\n            \"providers\": [\n                \"Akaunting\\\\Setting\\\\Provider\"\n            ],\n            \"aliases\": {\n                \"Setting\": \"Akaunting\\\\Setting\\\\Facade\"\n            }\n        }\n    },\n    \"minimum-stability\": \"dev\",\n    \"prefer-stable\": true\n}\n"
  },
  {
    "path": "phpunit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit backupGlobals=\"false\"\n         backupStaticAttributes=\"false\"\n         bootstrap=\"vendor/autoload.php\"\n         colors=\"true\"\n         convertErrorsToExceptions=\"true\"\n         convertNoticesToExceptions=\"true\"\n         convertWarningsToExceptions=\"true\"\n         processIsolation=\"false\"\n         stopOnFailure=\"false\"\n         syntaxCheck=\"false\"\n>\n    <testsuites>\n        <testsuite name=\"Package Test Suite\">\n            <directory suffix=\".php\">./tests/</directory>\n        </testsuite>\n    </testsuites>\n</phpunit>"
  },
  {
    "path": "src/Config/setting.php",
    "content": "<?php\n\nreturn [\n\n    /*\n    |--------------------------------------------------------------------------\n    | Enable / Disable auto save\n    |--------------------------------------------------------------------------\n    |\n    | Auto-save every time the application shuts down\n    |\n    */\n    'auto_save'         => false,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Cache\n    |--------------------------------------------------------------------------\n    |\n    | Options for caching. Set whether to enable cache, its key, time to live\n    | in seconds and whether to auto clear after save.\n    |\n    */\n    'cache' => [\n        'enabled'       => false,\n        'key'           => 'setting',\n        'ttl'           => 3600,\n        'auto_clear'    => true,\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Setting driver\n    |--------------------------------------------------------------------------\n    |\n    | Select where to store the settings.\n    |\n    | Supported: \"database\", \"json\", \"memory\"\n    |\n    */\n    'driver'            => 'database',\n\n    /*\n    |--------------------------------------------------------------------------\n    | Database driver\n    |--------------------------------------------------------------------------\n    |\n    | Options for database driver. Enter which connection to use, null means\n    | the default connection. Set the table and column names.\n    |\n    */\n    'database' => [\n        'connection'    => null,\n        'table'         => 'settings',\n        'key'           => 'key',\n        'value'         => 'value',\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | JSON driver\n    |--------------------------------------------------------------------------\n    |\n    | Options for json driver. Enter the full path to the .json file.\n    |\n    */\n    'json' => [\n        'path'          => storage_path() . '/settings.json',\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Override application config values\n    |--------------------------------------------------------------------------\n    |\n    | If defined, settings package will override these config values.\n    |\n    | Sample:\n    |   \"app.locale\" => \"settings.locale\",\n    |\n    */\n    'override' => [\n\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Fallback\n    |--------------------------------------------------------------------------\n    |\n    | Define fallback settings to be used in case the default is null\n    |\n    | Sample:\n    |   \"currency\" => \"USD\",\n    |\n    */\n    'fallback' => [\n\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Required Extra Columns\n    |--------------------------------------------------------------------------\n    |\n    | The list of columns required to be set up\n    |\n    | Sample:\n    |   \"user_id\",\n    |   \"tenant_id\",\n    |\n    */\n    'required_extra_columns' => [\n\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Encryption\n    |--------------------------------------------------------------------------\n    |\n    | Define the keys which should be crypt automatically.\n    |\n    | Sample:\n    |   \"payment.key\"\n    |\n    */\n   'encrypted_keys' => [\n\n   ],\n\n];\n"
  },
  {
    "path": "src/Contracts/Driver.php",
    "content": "<?php\n\nnamespace Akaunting\\Setting\\Contracts;\n\nuse Akaunting\\Setting\\Support\\Arr;\nuse Illuminate\\Support\\Facades\\Cache;\n\nabstract class Driver\n{\n    /**\n     * The settings data.\n     *\n     * @var array\n     */\n    protected $data = [];\n\n    /**\n     * Whether the store has changed since it was last loaded.\n     *\n     * @var bool\n     */\n    protected $unsaved = false;\n\n    /**\n     * Whether the settings data are loaded.\n     *\n     * @var bool\n     */\n    protected $loaded = false;\n\n    /**\n     * Include and merge with fallbacks\n     *\n     * @var bool\n     */\n    protected $with_fallback = true;\n\n    /**\n     * Excludes fallback data\n     */\n    public function withoutFallback()\n    {\n        $this->with_fallback = false;\n\n        return $this;\n    }\n\n    /**\n     * Get a specific key from the settings data.\n     *\n     * @param string|array $key\n     * @param mixed        $default Optional default value.\n     *\n     * @return mixed\n     */\n    public function get($key, $default = null)\n    {\n        if (!$this->checkExtraColumns()) {\n            return false;\n        }\n\n        $this->load();\n\n        return Arr::get($this->data, $key, $default);\n    }\n\n    /**\n     * Get the fallback value if default is null.\n     *\n     * @param string|array $key\n     * @param mixed        $default\n     *\n     * @return mixed\n     */\n    public function getFallback($key, $default = null)\n    {\n        if (($default !== null) || is_array($key)) {\n            return $default;\n        }\n\n        return Arr::get((array) config('setting.fallback'), $key);\n    }\n\n    /**\n     * Check if the given value is same as fallback.\n     *\n     * @param string $key\n     * @param string $value\n     *\n     * @return bool\n     */\n    public function isEqualToFallback($key, $value)\n    {\n        return (string) $this->getFallback($key) == (string) $value;\n    }\n\n    /**\n     * Determine if a key exists in the settings data.\n     *\n     * @param string $key\n     *\n     * @return bool\n     */\n    public function has($key)\n    {\n        if (!$this->checkExtraColumns()) {\n            return false;\n        }\n\n        $this->load();\n\n        return Arr::has($this->data, $key);\n    }\n\n    /**\n     * Set a specific key to a value in the settings data.\n     *\n     * @param string|array $key   Key string or associative array of key => value\n     * @param mixed        $value Optional only if the first argument is an array\n     */\n    public function set($key, $value = null)\n    {\n        if (!$this->checkExtraColumns()) {\n            return;\n        }\n\n        $this->load();\n        $this->unsaved = true;\n\n        if (is_array($key)) {\n            foreach ($key as $k => $v) {\n                Arr::set($this->data, $k, $v);\n            }\n        } else {\n            Arr::set($this->data, $key, $value);\n        }\n    }\n\n    /**\n     * Unset a key in the settings data.\n     *\n     * @param string $key\n     */\n    public function forget($key)\n    {\n        if (!$this->checkExtraColumns()) {\n            return;\n        }\n\n        $this->unsaved = true;\n\n        if ($this->has($key)) {\n            Arr::forget($this->data, $key);\n        }\n    }\n\n    /**\n     * Unset all keys in the settings data.\n     *\n     * @return void\n     */\n    public function forgetAll()\n    {\n        if (!$this->checkExtraColumns()) {\n            return;\n        }\n\n        if (config('setting.cache.enabled')) {\n            Cache::forget($this->getCacheKey());\n        }\n\n        $this->unsaved = true;\n        $this->data = [];\n    }\n\n    /**\n     * Get all settings data.\n     *\n     * @return array|bool\n     */\n    public function all()\n    {\n        if (!$this->checkExtraColumns()) {\n            return [];\n        }\n\n        $this->load();\n\n        return $this->data;\n    }\n\n    /**\n     * Save any changes done to the settings data.\n     *\n     * @return void\n     */\n    public function save()\n    {\n        if (!$this->checkExtraColumns()) {\n            return;\n        }\n\n        if (!$this->unsaved) {\n            // either nothing has been changed, or data has not been loaded, so\n            // do nothing by returning early\n            return;\n        }\n\n        if (config('setting.cache.enabled') && config('setting.cache.auto_clear')) {\n            Cache::forget($this->getCacheKey());\n        }\n\n        $this->write($this->data);\n        $this->unsaved = false;\n    }\n\n    /**\n     * Make sure data is loaded.\n     *\n     * @param $force Force a reload of data. Default false.\n     */\n    public function load($force = false)\n    {\n        if (!$this->checkExtraColumns()) {\n            return;\n        }\n\n        if ($this->loaded && !$force) {\n            return;\n        }\n\n        $fallback_data = $this->with_fallback ? config('setting.fallback') : [];\n        $driver_data = $this->readData();\n\n        $this->data = Arr::merge((array) $fallback_data, (array) $driver_data);\n        $this->loaded = true;\n    }\n\n    /**\n     * Read data from driver or cache\n     *\n     * @return array\n     */\n    public function readData()\n    {\n        if (config('setting.cache.enabled')) {\n            return $this->readDataFromCache();\n        }\n\n        return $this->read();\n    }\n\n    /**\n     * Read data from cache\n     *\n     * @return array\n     */\n    public function readDataFromCache()\n    {\n        return Cache::remember($this->getCacheKey(), config('setting.cache.ttl'), function () {\n            return $this->read();\n        });\n    }\n\n    /**\n     * Check if extra columns are set up.\n     *\n     * @return boolean\n     */\n    public function checkExtraColumns()\n    {\n        if (!$required_extra_columns = config('setting.required_extra_columns')) {\n            return true;\n        }\n\n        if (array_keys_exists($required_extra_columns, $this->getExtraColumns())) {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Get cache key based on extra columns.\n     *\n     * @return string\n     */\n    public function getCacheKey()\n    {\n        $key = config('setting.cache.key');\n\n        foreach ($this->getExtraColumns() as $name => $value) {\n            $key .= '_' . $name . '_' . $value;\n        }\n\n        return $key;\n    }\n\n    /**\n     * Get extra columns added to the rows.\n     *\n     * @return array\n     */\n    abstract protected function getExtraColumns();\n\n    /**\n     * Read data from driver.\n     *\n     * @return array\n     */\n    abstract protected function read();\n\n    /**\n     * Write data to driver.\n     *\n     * @param  array  $data\n     *\n     * @return void\n     */\n    abstract protected function write(array $data);\n}\n"
  },
  {
    "path": "src/Drivers/Database.php",
    "content": "<?php\n\nnamespace Akaunting\\Setting\\Drivers;\n\nuse Akaunting\\Setting\\Contracts\\Driver;\nuse Akaunting\\Setting\\Support\\Arr;\nuse Closure;\nuse Illuminate\\Database\\Connection;\nuse Illuminate\\Support\\Arr as LaravelArr;\nuse Illuminate\\Support\\Facades\\Crypt;\n\nclass Database extends Driver\n{\n    /**\n     * The database connection instance.\n     *\n     * @var \\Illuminate\\Database\\Connection\n     */\n    protected $connection;\n\n    /**\n     * The table to query from.\n     *\n     * @var string\n     */\n    protected $table;\n\n    /**\n     * The key column name to query from.\n     *\n     * @var string\n     */\n    protected $key;\n\n    /**\n     * The value column name to query from.\n     *\n     * @var string\n     */\n    protected $value;\n\n    /**\n     * Keys which should be encrypt automatically.\n     *\n     * @var array\n     */\n    protected $encrypted_keys;\n\n    /**\n     * Any query constraints that should be applied.\n     *\n     * @var Closure|null\n     */\n    protected $query_constraint;\n\n    /**\n     * Any extra columns that should be added to the rows.\n     *\n     * @var array\n     */\n    protected $extra_columns = [];\n\n    /**\n     * @param \\Illuminate\\Database\\Connection $connection\n     * @param string $table\n     */\n    public function __construct(Connection $connection, $table = null, $key = null, $value = null, array $encrypted_keys = [])\n    {\n        $this->connection = $connection;\n        $this->table = $table ?: 'settings';\n        $this->key = $key ?: 'key';\n        $this->value = $value ?: 'value';\n        $this->encrypted_keys = $encrypted_keys;\n    }\n\n    /**\n     * Set the table to query from.\n     *\n     * @param string $table\n     */\n    public function setTable($table)\n    {\n        $this->table = $table;\n    }\n\n    /**\n     * Set the key column name to query from.\n     *\n     * @param string $key\n     */\n    public function setKey($key)\n    {\n        $this->key = $key;\n    }\n\n    /**\n     * Set the value column name to query from.\n     *\n     * @param string $value\n     */\n    public function setValue($value)\n    {\n        $this->value = $value;\n    }\n\n    /**\n     * Set the query constraint.\n     *\n     * @param Closure $callback\n     */\n    public function setConstraint(Closure $callback)\n    {\n        $this->data = [];\n        $this->loaded = false;\n        $this->query_constraint = $callback;\n    }\n\n    /**\n     * Set extra columns to be added to the rows.\n     *\n     * @param array $columns\n     */\n    public function setExtraColumns(array $columns)\n    {\n        $this->extra_columns = $columns;\n    }\n\n    /**\n     * Get extra columns added to the rows.\n     *\n     * @return array\n     */\n    public function getExtraColumns()\n    {\n        return $this->extra_columns;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function forget($key)\n    {\n        parent::forget($key);\n\n        // because the database driver cannot store empty arrays, remove empty\n        // arrays to keep data consistent before and after saving\n        $segments = explode('.', $key);\n        array_pop($segments);\n\n        while ($segments) {\n            $segment = implode('.', $segments);\n\n            // non-empty array - exit out of the loop\n            if ($this->get($segment)) {\n                break;\n            }\n\n            // remove the empty array and move on to the next segment\n            $this->forget($segment);\n            array_pop($segments);\n        }\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function write(array $data)\n    {\n        // Get current data\n        $db_data = $this->newQuery()->get([$this->key, $this->value])->toArray();\n\n        $insert_data = LaravelArr::dot($data);\n        $update_data = [];\n        $delete_keys = [];\n\n        foreach ($db_data as $db_row) {\n            $key = $db_row->{$this->key};\n            $value = $db_row->{$this->value};\n\n            $is_in_insert = $is_different_in_db = $is_same_as_fallback = false;\n\n            if (isset($insert_data[$key])) {\n                $is_in_insert = true;\n                $is_different_in_db = (string) $insert_data[$key] != (string) $value;\n                $is_same_as_fallback = $this->isEqualToFallback($key, $insert_data[$key]);\n            }\n\n            if ($is_in_insert) {\n                if ($is_same_as_fallback) {\n                    // Delete if new data is same as fallback\n                    $delete_keys[] = $key;\n                } elseif ($is_different_in_db) {\n                    // Update if new data is different from db\n                    $update_data[$key] = $insert_data[$key];\n                }\n            } else {\n                // Delete if current db not available in new data\n                $delete_keys[] = $key;\n            }\n\n            unset($insert_data[$key]);\n        }\n\n        foreach ($update_data as $key => $value) {\n            $value = $this->prepareValue($key, $value);\n\n            $this->newQuery()\n                ->where($this->key, '=', $key)\n                ->update([$this->value => $value]);\n        }\n\n        if ($insert_data) {\n            $this->newQuery(true)\n                ->insert($this->prepareInsertData($insert_data));\n        }\n\n        if ($delete_keys) {\n            $this->newQuery()\n                ->whereIn($this->key, $delete_keys)\n                ->delete();\n        }\n    }\n\n    /**\n     * Transforms settings data into an array ready to be insterted into the\n     * database. Call array_dot on a multidimensional array before passing it\n     * into this method!\n     *\n     * @param array $data Call array_dot on a multidimensional array before passing it into this method!\n     *\n     * @return array\n     */\n    protected function prepareInsertData(array $data)\n    {\n        $db_data = [];\n\n        if ($this->getExtraColumns()) {\n            foreach ($data as $key => $value) {\n                $value = $this->prepareValue($key, $value);\n\n                // Don't insert if same as fallback\n                if ($this->isEqualToFallback($key, $value)) {\n                    continue;\n                }\n\n                $db_data[] = array_merge(\n                    $this->getExtraColumns(),\n                    [$this->key => $key, $this->value => $value]\n                );\n            }\n        } else {\n            foreach ($data as $key => $value) {\n                $value = $this->prepareValue($key, $value);\n\n                // Don't insert if same as fallback\n                if ($this->isEqualToFallback($key, $value)) {\n                    continue;\n                }\n\n                $db_data[] = [$this->key => $key, $this->value => $value];\n            }\n        }\n\n        return $db_data;\n    }\n\n    /**\n     * Checks if the provided key should be encrypted or not.\n     * Also type casts the given value to a string so errors with booleans or integers are handeled.\n     * Otherwise it returns the original value.\n     *\n     * @param  string $key   Key to check if it's inside the encryptedValues variable.\n     * @param  mixed $value  Info: Encryption only supports strings.\n     *\n     * @return string\n     */\n    protected function prepareValue(string $key, $value)\n    {\n        // Check if key should be encrypted\n        if (in_array($key, $this->encrypted_keys)) {\n            // Cast to string to avoid error when a user passes a boolean value\n            return Crypt::encryptString((string) $value);\n        }\n\n        return $value;\n    }\n\n    /**\n     * Checks if the provided key should be decrypted or not.\n     * Otherwise it returns the original value.\n     *\n     * @param  string $key   Key to check if it's inside the encryptedValues variable.\n     * @param  mixed $value  Info: Encryption only supports strings.\n     *\n     * @return string\n     */\n    protected function unpackValue(string $key, $value)\n    {\n        // Check if key should be encrypted\n        if (in_array($key, $this->encrypted_keys)) {\n            // Cast to string to avoid error when a user passes a boolean value\n            return Crypt::decryptString((string) $value);\n        }\n\n        return $value;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function read()\n    {\n        return $this->parseReadData($this->newQuery()->get());\n    }\n\n    /**\n     * Parse data coming from the database.\n     *\n     * @param array $data\n     *\n     * @return array\n     */\n    public function parseReadData($data)\n    {\n        $results = [];\n\n        foreach ($data as $row) {\n            if (is_array($row)) {\n                $key = $row[$this->key];\n                $value = $row[$this->value];\n            } elseif (is_object($row)) {\n                $key = $row->{$this->key};\n                $value = $row->{$this->value};\n            } else {\n                $msg = 'Expected array or object, got ' . gettype($row);\n                throw new \\UnexpectedValueException($msg);\n            }\n\n            // Encryption\n            $value = $this->unpackValue($key, $value);\n\n            Arr::set($results, $key, $value);\n        }\n\n        return $results;\n    }\n\n    /**\n     * Create a new query builder instance.\n     *\n     * @param bool $insert\n     *\n     * @return \\Illuminate\\Database\\Query\\Builder\n     */\n    protected function newQuery($insert = false)\n    {\n        $query = $this->connection->table($this->table);\n\n        if (!$insert) {\n            foreach ($this->getExtraColumns() as $key => $value) {\n                $query->where($key, '=', $value);\n            }\n        }\n\n        if ($this->query_constraint !== null) {\n            $callback = $this->query_constraint;\n            $callback($query, $insert);\n        }\n\n        return $query;\n    }\n}\n"
  },
  {
    "path": "src/Drivers/Json.php",
    "content": "<?php\n\nnamespace Akaunting\\Setting\\Drivers;\n\nuse Akaunting\\Setting\\Contracts\\Driver;\nuse Illuminate\\Filesystem\\Filesystem;\n\nclass Json extends Driver\n{\n    /**\n     * @param \\Illuminate\\Filesystem\\Filesystem $files\n     * @param string                           $path\n     */\n    public function __construct(Filesystem $files, $path = null)\n    {\n        $this->files = $files;\n\n        $this->setPath($path ?: storage_path() . '/settings.json');\n    }\n\n    /**\n     * Set the path for the JSON file.\n     *\n     * @param string $path\n     */\n    public function setPath($path)\n    {\n        // If the file does not already exist, we will attempt to create it.\n        if (!$this->files->exists($path)) {\n            $result = $this->files->put($path, '{}');\n            if ($result === false) {\n                throw new \\InvalidArgumentException(\"Could not write to $path.\");\n            }\n        }\n\n        if (!$this->files->isWritable($path)) {\n            throw new \\InvalidArgumentException(\"$path is not writable.\");\n        }\n\n        $this->path = $path;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getExtraColumns()\n    {\n        return [];\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function read()\n    {\n        $contents = $this->files->get($this->path);\n\n        $data = json_decode($contents, true);\n\n        if ($data === null) {\n            throw new \\RuntimeException(\"Invalid JSON in {$this->path}\");\n        }\n\n        return $data;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function write(array $data)\n    {\n        if ($data) {\n            $contents = json_encode($data);\n        } else {\n            $contents = '{}';\n        }\n\n        $this->files->put($this->path, $contents);\n    }\n}\n"
  },
  {
    "path": "src/Drivers/Memory.php",
    "content": "<?php\n\nnamespace Akaunting\\Setting\\Drivers;\n\nuse Akaunting\\Setting\\Contracts\\Driver;\n\nclass Memory extends Driver\n{\n    /**\n     * @param array $data\n     */\n    public function __construct(array $data = null)\n    {\n        if ($data) {\n            $this->data = $data;\n        }\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getExtraColumns()\n    {\n        return [];\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function read()\n    {\n        return $this->data;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function write(array $data)\n    {\n        // do nothing\n    }\n}\n"
  },
  {
    "path": "src/Facade.php",
    "content": "<?php\n\nnamespace Akaunting\\Setting;\n\nuse Illuminate\\Support\\Facades\\Facade as BaseFacade;\n\nclass Facade extends BaseFacade\n{\n    /**\n     * Get the registered name of the component.\n     */\n    public static function getFacadeAccessor()\n    {\n        return 'setting';\n    }\n}\n"
  },
  {
    "path": "src/Manager.php",
    "content": "<?php\n\nnamespace Akaunting\\Setting;\n\nuse Akaunting\\Setting\\Drivers\\Database;\nuse Akaunting\\Setting\\Drivers\\Json;\nuse Akaunting\\Setting\\Drivers\\Memory;\nuse Illuminate\\Support\\Manager as BaseManager;\n\nclass Manager extends BaseManager\n{\n    /**\n     * The container instance.\n     *\n     * @var \\Illuminate\\Contracts\\Container\\Container\n     */\n    protected $container;\n\n    /**\n     * The application instance.\n     *\n     * @param \\Illuminate\\Contracts\\Foundation\\Application $app\n     */\n    public function __construct($app = null)\n    {\n        $this->container = $app ?? app();\n\n        parent::__construct($this->container);\n    }\n\n    public function getDefaultDriver()\n    {\n        return config('setting.driver');\n    }\n\n    public function createJsonDriver()\n    {\n        $path = config('setting.json.path');\n\n        return new Json($this->container['files'], $path);\n    }\n\n    public function createDatabaseDriver()\n    {\n        $connection = $this->container['db']->connection(config('setting.database.connection'));\n        $table = config('setting.database.table');\n        $key = config('setting.database.key');\n        $value = config('setting.database.value');\n        $encryptedKeys = config('setting.encrypted_keys');\n\n        return new Database($connection, $table, $key, $value, $encryptedKeys);\n    }\n\n    public function createMemoryDriver()\n    {\n        return new Memory();\n    }\n\n    public function createArrayDriver()\n    {\n        return $this->createMemoryDriver();\n    }\n}\n"
  },
  {
    "path": "src/Middleware/AutoSaveSetting.php",
    "content": "<?php\n\nnamespace Akaunting\\Setting\\Middleware;\n\nuse Closure;\n\nclass AutoSaveSetting\n{\n    /**\n     * Create a new save settings middleware.\n     */\n    public function __construct()\n    {\n        $this->setting = app('setting');\n    }\n\n    /**\n     * Handle an incoming request.\n     *\n     * @param \\Illuminate\\Http\\Request $request\n     * @param \\Closure $next\n     *\n     * @return mixed\n     */\n    public function handle($request, Closure $next)\n    {\n        $response = $next($request);\n\n        $this->setting->save();\n\n        return $response;\n    }\n}\n"
  },
  {
    "path": "src/Migrations/2017_08_24_000000_create_settings_table.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nclass CreateSettingsTable extends Migration\n{\n    /**\n     * Set up the options.\n     */\n    public function __construct()\n    {\n        $this->table = config('setting.database.table');\n        $this->key = config('setting.database.key');\n        $this->value = config('setting.database.value');\n    }\n\n    /**\n     * Run the migrations.\n     *\n     * @return void\n     */\n    public function up()\n    {\n        Schema::create($this->table, function (Blueprint $table) {\n            $table->increments('id');\n            $table->string($this->key)->index();\n            $table->text($this->value);\n        });\n    }\n\n    /**\n     * Reverse the migrations.\n     *\n     * @return void\n     */\n    public function down()\n    {\n        Schema::drop($this->table);\n    }\n}\n"
  },
  {
    "path": "src/Provider.php",
    "content": "<?php\n\nnamespace Akaunting\\Setting;\n\nuse Akaunting\\Setting\\Middleware\\AutoSaveSetting;\nuse Illuminate\\Support\\ServiceProvider;\nuse Illuminate\\Support\\Arr;\nuse Illuminate\\View\\Compilers\\BladeCompiler;\n\nclass Provider extends ServiceProvider\n{\n    /**\n     * Bootstrap the application services.\n     *\n     * @return void\n     */\n    public function boot()\n    {\n        $this->publishes([\n            __DIR__ . '/Config/setting.php'                                     => config_path('setting.php'),\n            __DIR__ . '/Migrations/2017_08_24_000000_create_settings_table.php' => database_path('migrations/2017_08_24_000000_create_settings_table.php'),\n        ], 'setting');\n\n        // Auto save setting\n        if (config('setting.auto_save')) {\n            $kernel = $this->app['Illuminate\\Contracts\\Http\\Kernel'];\n            $kernel->pushMiddleware(AutoSaveSetting::class);\n        }\n\n        $this->override();\n\n        // Register blade directive\n        $this->callAfterResolving('blade.compiler', function (BladeCompiler $compiler) {\n            $compiler->directive('setting', function ($expression) {\n                return \"<?php echo setting($expression); ?>\";\n            });\n        });\n    }\n\n    /**\n     * Register the application services.\n     *\n     * @return void\n     */\n    public function register()\n    {\n        $this->app->singleton('setting.manager', function ($app) {\n            return new Manager($app);\n        });\n\n        $this->app->singleton('setting', function ($app) {\n            return $app['setting.manager']->driver();\n        });\n\n        $this->mergeConfigFrom(__DIR__ . '/Config/setting.php', 'setting');\n    }\n\n    private function override()\n    {\n        $override = config('setting.override', []);\n\n        foreach (Arr::dot($override) as $config_key => $setting_key) {\n            $config_key = is_string($config_key) ? $config_key : $setting_key;\n\n            try {\n                if (! is_null($value = setting($setting_key))) {\n                    config([$config_key => $value]);\n                }\n            } catch (\\Exception $e) {\n                continue;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Support/Arr.php",
    "content": "<?php\n\nnamespace Akaunting\\Setting\\Support;\n\nclass Arr\n{\n    /**\n     * This class is a static class and should not be instantiated.\n     */\n    private function __construct()\n    {\n        //\n    }\n\n    /**\n     * Get an element from an array.\n     *\n     * @param array $data\n     * @param string $key     Specify a nested element by separating keys with full stops.\n     * @param mixed $default If the element is not found, return this.\n     *\n     * @return mixed\n     */\n    public static function get(array $data, $key, $default = null)\n    {\n        if ($key === null) {\n            return $data;\n        }\n\n        if (is_array($key)) {\n            return static::getArray($data, $key, $default);\n        }\n\n        foreach (explode('.', $key) as $segment) {\n            if (!is_array($data)) {\n                return $default;\n            }\n\n            if (!array_key_exists($segment, $data)) {\n                return $default;\n            }\n\n            $data = $data[$segment];\n        }\n\n        return $data;\n    }\n\n    protected static function getArray(array $input, $keys, $default = null)\n    {\n        $output = [];\n\n        foreach ($keys as $key) {\n            static::set($output, $key, static::get($input, $key, $default));\n        }\n\n        return $output;\n    }\n\n    /**\n     * Determine if an array has a given key.\n     *\n     * @param array $data\n     * @param string $key\n     *\n     * @return bool\n     */\n    public static function has(array $data, $key)\n    {\n        foreach (explode('.', $key) as $segment) {\n            if (!is_array($data)) {\n                return false;\n            }\n\n            if (!array_key_exists($segment, $data)) {\n                return false;\n            }\n\n            $data = $data[$segment];\n        }\n\n        return true;\n    }\n\n    /**\n     * Set an element of an array.\n     *\n     * @param array $data\n     * @param string $key   Specify a nested element by separating keys with full stops.\n     * @param mixed $value\n     */\n    public static function set(array &$data, $key, $value)\n    {\n        $segments = explode('.', $key);\n\n        $key = array_pop($segments);\n\n        // iterate through all of $segments except the last one\n        foreach ($segments as $segment) {\n            if (!array_key_exists($segment, $data)) {\n                $data[$segment] = array();\n            } elseif (!is_array($data[$segment])) {\n                throw new \\UnexpectedValueException('Non-array segment encountered');\n            }\n\n            $data = &$data[$segment];\n        }\n\n        $data[$key] = $value;\n    }\n\n    /**\n     * Unset an element from an array.\n     *\n     * @param array &$data\n     * @param string $key   Specify a nested element by separating keys with full stops.\n     */\n    public static function forget(array &$data, $key)\n    {\n        $segments = explode('.', $key);\n\n        $key = array_pop($segments);\n\n        // iterate through all of $segments except the last one\n        foreach ($segments as $segment) {\n            if (!array_key_exists($segment, $data)) {\n                return;\n            } elseif (!is_array($data[$segment])) {\n                throw new \\UnexpectedValueException('Non-array segment encountered');\n            }\n\n            $data = &$data[$segment];\n        }\n\n        unset($data[$key]);\n    }\n\n    /**\n     * Merge two multidimensional arrays recursive\n     *\n     * @param array $array_1\n     * @param array $array_2\n     *\n     * @return array\n     */\n    public static function merge(array $array_1, array $array_2)\n    {\n        $merged = $array_1;\n\n        foreach ($array_2 as $key => $value) {\n            if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {\n                $merged[$key] = static::merge($merged[$key], $value);\n            } elseif (is_numeric($key)) {\n                if (!in_array($value, $merged)) {\n                    $merged[] = $value;\n                }\n            } else {\n                $merged[$key] = $value;\n            }\n        }\n\n        return $merged;\n    }\n}\n"
  },
  {
    "path": "src/helpers.php",
    "content": "<?php\n\nif (!function_exists('array_keys_exists')) {\n    /**\n     * Easily check if multiple array keys exist.\n     *\n     * @param array $keys\n     * @param array $arr\n     *\n     * @return boolean\n     */\n    function array_keys_exists(array $keys, array $arr)\n    {\n        return !array_diff_key(array_flip($keys), $arr);\n    }\n}\n\nif (!function_exists('setting')) {\n    /**\n     * Get / set the specified setting value.\n     *\n     * If an array is passed as the key, we will assume you want to set an array of values.\n     *\n     * @param array|string $key\n     * @param mixed $default\n     *\n     * @return mixed\n     */\n    function setting($key = null, $default = null)\n    {\n        $setting = app('setting');\n\n        if (is_null($key)) {\n            return $setting;\n        }\n\n        if (is_array($key)) {\n            $setting->set($key);\n\n            return $setting;\n        }\n\n        return $setting->get($key, $default);\n    }\n}\n"
  },
  {
    "path": "tests/functional/AbstractFunctionalTest.php",
    "content": "<?php\n\nuse Akaunting\\Setting\\Drivers\\Database;\n\nabstract class AbstractFunctionalTest extends PHPUnit_Framework_TestCase\n{\n    abstract protected function createStore(array $data = []);\n\n    protected function assertStoreEquals($store, $expected, $message = null)\n    {\n        $this->assertEquals($expected, $store->all(), $message);\n        $store->save();\n        $store = $this->createStore();\n        $this->assertEquals($expected, $store->all(), $message);\n    }\n\n    protected function assertStoreKeyEquals($store, $key, $expected, $message = null)\n    {\n        $this->assertEquals($expected, $store->get($key), $message);\n        $store->save();\n        $store = $this->createStore();\n        $this->assertEquals($expected, $store->get($key), $message);\n    }\n\n    /** @test */\n    public function store_is_initially_empty()\n    {\n        $store = $this->createStore();\n        $this->assertEquals([], $store->all());\n    }\n\n    /** @test */\n    public function written_changes_are_saved()\n    {\n        $store = $this->createStore();\n        $store->set('foo', 'bar');\n        $this->assertStoreKeyEquals($store, 'foo', 'bar');\n    }\n\n    /** @test */\n    public function nested_keys_are_nested()\n    {\n        $store = $this->createStore();\n        $store->set('foo.bar', 'baz');\n        $this->assertStoreEquals($store, ['foo' => ['bar' => 'baz']]);\n    }\n\n    /** @test */\n    public function cannot_set_nested_key_on_non_array_member()\n    {\n        $store = $this->createStore();\n        $store->set('foo', 'bar');\n        $this->setExpectedException('UnexpectedValueException', 'Non-array segment encountered');\n        $store->set('foo.bar', 'baz');\n    }\n\n    /** @test */\n    public function can_forget_key()\n    {\n        $store = $this->createStore();\n        $store->set('foo', 'bar');\n        $store->set('bar', 'baz');\n        $this->assertStoreEquals($store, ['foo' => 'bar', 'bar' => 'baz']);\n\n        $store->forget('foo');\n        $this->assertStoreEquals($store, ['bar' => 'baz']);\n    }\n\n    /** @test */\n    public function can_forget_nested_key()\n    {\n        $store = $this->createStore();\n        $store->set('foo.bar', 'baz');\n        $store->set('foo.baz', 'bar');\n        $store->set('bar.foo', 'baz');\n        $this->assertStoreEquals($store, [\n            'foo' => [\n                'bar' => 'baz',\n                'baz' => 'bar',\n            ],\n            'bar' => [\n                'foo' => 'baz',\n            ],\n        ]);\n\n        $store->forget('foo.bar');\n        $this->assertStoreEquals($store, [\n            'foo' => [\n                'baz' => 'bar',\n            ],\n            'bar' => [\n                'foo' => 'baz',\n            ],\n        ]);\n\n        $store->forget('bar.foo');\n        $expected = [\n            'foo' => [\n                'baz' => 'bar',\n            ],\n            'bar' => [\n            ],\n        ];\n        if ($store instanceof Database) {\n            unset($expected['bar']);\n        }\n        $this->assertStoreEquals($store, $expected);\n    }\n\n    /** @test */\n    public function can_forget_all()\n    {\n        $store = $this->createStore(['foo' => 'bar']);\n        $this->assertStoreEquals($store, ['foo' => 'bar']);\n        $store->forgetAll();\n        $this->assertStoreEquals($store, []);\n    }\n}\n"
  },
  {
    "path": "tests/functional/DatabaseTest.php",
    "content": "<?php\n\nclass DatabaseTest extends AbstractFunctionalTest\n{\n    public function setUp()\n    {\n        $this->container = new \\Illuminate\\Container\\Container();\n        $this->capsule = new \\Illuminate\\Database\\Capsule\\Manager($this->container);\n        $this->capsule->setAsGlobal();\n        $this->container['db'] = $this->capsule;\n        $this->capsule->addConnection([\n            'driver'   => 'sqlite',\n            'database' => ':memory:',\n            'prefix'   => '',\n        ]);\n\n        $this->capsule->schema()->create('settings', function ($t) {\n            $t->string('key', 64)->unique();\n            $t->string('value', 4096);\n        });\n    }\n\n    public function tearDown()\n    {\n        $this->capsule->schema()->drop('settings');\n        unset($this->capsule);\n        unset($this->container);\n    }\n\n    protected function createStore(array $data = [])\n    {\n        if ($data) {\n            $store = $this->createStore();\n            $store->set($data);\n            $store->save();\n            unset($store);\n        }\n\n        return new \\Akaunting\\Setting\\Drivers\\Database(\n            $this->capsule->getConnection()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/functional/JsonTest.php",
    "content": "<?php\n\nclass JsonTest extends AbstractFunctionalTest\n{\n    protected function createStore(array $data = null)\n    {\n        $path = dirname(__DIR__) . '/tmp/store.json';\n\n        if ($data !== null) {\n            if ($data) {\n                $json = json_encode($data);\n            } else {\n                $json = '{}';\n            }\n\n            file_put_contents($path, $json);\n        }\n\n        return new \\Akaunting\\Setting\\Drivers\\Json(\n            new \\Illuminate\\Filesystem\\Filesystem(),\n            $path\n        );\n    }\n\n    public function tearDown()\n    {\n        $path = dirname(__DIR__) . '/tmp/store.json';\n        unlink($path);\n    }\n}\n"
  },
  {
    "path": "tests/functional/MemoryTest.php",
    "content": "<?php\n\nclass MemoryTest extends AbstractFunctionalTest\n{\n    protected function assertStoreEquals($store, $expected, $message = null)\n    {\n        $this->assertEquals($expected, $store->all(), $message);\n        // removed persistance test assertions\n    }\n\n    protected function assertStoreKeyEquals($store, $key, $expected, $message = null)\n    {\n        $this->assertEquals($expected, $store->get($key), $message);\n        // removed persistance test assertions\n    }\n\n    protected function createStore(array $data = null)\n    {\n        return new \\Akaunting\\Setting\\Drivers\\Memory($data);\n    }\n}\n"
  },
  {
    "path": "tests/unit/ArrayUtilTest.php",
    "content": "<?php\n\nuse Akaunting\\Setting\\Support\\Arr;\n\nclass ArrayUtilityTest extends PHPUnit_Framework_TestCase\n{\n    /**\n     * @test\n     * @dataProvider getGetData\n     */\n    public function getReturnsCorrectValue(array $data, $key, $expected)\n    {\n        $this->assertEquals($expected, Arr::get($data, $key));\n    }\n\n    public function getGetData()\n    {\n        return [\n            [[], 'foo', null],\n            [['foo' => 'bar'], 'foo', 'bar'],\n            [['foo' => 'bar'], 'bar', null],\n            [['foo' => 'bar'], 'foo.bar', null],\n            [['foo' => ['bar' => 'baz']], 'foo.bar', 'baz'],\n            [['foo' => ['bar' => 'baz']], 'foo.baz', null],\n            [['foo' => ['bar' => 'baz']], 'foo', ['bar' => 'baz']],\n            [\n                ['foo' => 'bar', 'bar' => 'baz'],\n                ['foo', 'bar'],\n                ['foo' => 'bar', 'bar' => 'baz'],\n            ],\n            [\n                ['foo' => ['bar' => 'baz'], 'bar' => 'baz'],\n                ['foo.bar', 'bar'],\n                ['foo' => ['bar' => 'baz'], 'bar' => 'baz'],\n            ],\n            [\n                ['foo' => ['bar' => 'baz'], 'bar' => 'baz'],\n                ['foo.bar'],\n                ['foo' => ['bar' => 'baz']],\n            ],\n            [\n                ['foo' => ['bar' => 'baz'], 'bar' => 'baz'],\n                ['foo.bar', 'baz'],\n                ['foo' => ['bar' => 'baz'], 'baz' => null],\n            ],\n        ];\n    }\n\n    /**\n     * @test\n     * @dataProvider getSetData\n     */\n    public function setSetsCorrectKeyToValue(array $input, $key, $value, array $expected)\n    {\n        Arr::set($input, $key, $value);\n        $this->assertEquals($expected, $input);\n    }\n\n    public function getSetData()\n    {\n        return [\n            [\n                ['foo' => 'bar'],\n                'foo',\n                'baz',\n                ['foo' => 'baz'],\n            ],\n            [\n                [],\n                'foo',\n                'bar',\n                ['foo' => 'bar'],\n            ],\n            [\n                [],\n                'foo.bar',\n                'baz',\n                ['foo' => ['bar' => 'baz']],\n            ],\n            [\n                ['foo' => ['bar' => 'baz']],\n                'foo.baz',\n                'foo',\n                ['foo' => ['bar' => 'baz', 'baz' => 'foo']],\n            ],\n            [\n                ['foo' => ['bar' => 'baz']],\n                'foo.baz.bar',\n                'baz',\n                ['foo' => ['bar' => 'baz', 'baz' => ['bar' => 'baz']]],\n            ],\n            [\n                [],\n                'foo.bar.baz',\n                'foo',\n                ['foo' => ['bar' => ['baz' => 'foo']]],\n            ],\n        ];\n    }\n\n    /** @test */\n    public function setThrowsExceptionOnNonArraySegment()\n    {\n        $data = ['foo' => 'bar'];\n        $this->setExpectedException('UnexpectedValueException', 'Non-array segment encountered');\n        Arr::set($data, 'foo.bar', 'baz');\n    }\n\n    /**\n     * @test\n     * @dataProvider getHasData\n     */\n    public function hasReturnsCorrectly(array $input, $key, $expected)\n    {\n        $this->assertEquals($expected, Arr::has($input, $key));\n    }\n\n    public function getHasData()\n    {\n        return [\n            [[], 'foo', false],\n            [['foo' => 'bar'], 'foo', true],\n            [['foo' => 'bar'], 'bar', false],\n            [['foo' => 'bar'], 'foo.bar', false],\n            [['foo' => ['bar' => 'baz']], 'foo.bar', true],\n            [['foo' => ['bar' => 'baz']], 'foo.baz', false],\n            [['foo' => ['bar' => 'baz']], 'foo', true],\n            [['foo' => null], 'foo', true],\n            [['foo' => ['bar' => null]], 'foo.bar', true],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/unit/DatabaseDriverTest.php",
    "content": "<?php\n\nuse Mockery as m;\n\nclass DatabaseDriverTest extends PHPUnit_Framework_TestCase\n{\n    public function tearDown()\n    {\n        m::close();\n    }\n\n    /** @test */\n    public function correct_data_is_inserted_and_updated()\n    {\n        $connection = $this->mockConnection();\n        $query = $this->mockQuery($connection);\n\n        $query->shouldReceive('get')->once()->andReturn([\n            ['key' => 'nest.one', 'value' => 'old'],\n        ]);\n        $query->shouldReceive('lists')->atMost(1)->andReturn(['nest.one']);\n        $query->shouldReceive('pluck')->atMost(1)->andReturn(['nest.one']);\n        $dbData = $this->getDbData();\n        unset($dbData[1]); // remove the nest.one array member\n        $query->shouldReceive('where')->with('key', '=', 'nest.one')->andReturn(m::self())->getMock()\n            ->shouldReceive('update')->with(['value' => 'nestone']);\n        $self = $this; // 5.3 compatibility\n        $query->shouldReceive('insert')->once()->andReturnUsing(function ($arg) use ($dbData, $self) {\n            $self->assertEquals(count($dbData), count($arg));\n            foreach ($dbData as $key => $value) {\n                $self->assertContains($value, $arg);\n            }\n        });\n\n        $store = $this->makeStore($connection);\n        $store->set('foo', 'bar');\n        $store->set('nest.one', 'nestone');\n        $store->set('nest.two', 'nesttwo');\n        $store->set('array', ['one', 'two']);\n        $store->save();\n    }\n\n    /** @test */\n    public function extra_columns_are_queried()\n    {\n        $connection = $this->mockConnection();\n        $query = $this->mockQuery($connection);\n        $query->shouldReceive('where')->once()->with('foo', '=', 'bar')\n            ->andReturn(m::self())->getMock()\n            ->shouldReceive('get')->once()->andReturn([\n                ['key' => 'foo', 'value' => 'bar'],\n            ]);\n\n        $store = $this->makeStore($connection);\n        $store->setExtraColumns(['foo' => 'bar']);\n        $this->assertEquals('bar', $store->get('foo'));\n    }\n\n    /** @test */\n    public function extra_columns_are_inserted()\n    {\n        $connection = $this->mockConnection();\n        $query = $this->mockQuery($connection);\n        $query->shouldReceive('where')->times(2)->with('extracol', '=', 'extradata')\n            ->andReturn(m::self());\n        $query->shouldReceive('get')->once()->andReturn([]);\n        $query->shouldReceive('lists')->atMost(1)->andReturn([]);\n        $query->shouldReceive('pluck')->atMost(1)->andReturn([]);\n        $query->shouldReceive('insert')->once()->with([\n            ['key' => 'foo', 'value' => 'bar', 'extracol' => 'extradata'],\n        ]);\n\n        $store = $this->makeStore($connection);\n        $store->setExtraColumns(['extracol' => 'extradata']);\n        $store->set('foo', 'bar');\n        $store->save();\n    }\n\n    protected function getDbData()\n    {\n        return [\n            ['key' => 'foo', 'value' => 'bar'],\n            ['key' => 'nest.one', 'value' => 'nestone'],\n            ['key' => 'nest.two', 'value' => 'nesttwo'],\n            ['key' => 'array.0', 'value' => 'one'],\n            ['key' => 'array.1', 'value' => 'two'],\n        ];\n    }\n\n    protected function mockConnection()\n    {\n        return m::mock('Illuminate\\Database\\Connection');\n    }\n\n    protected function mockQuery($connection)\n    {\n        $query = m::mock('Illuminate\\Database\\Query\\Builder');\n        $connection->shouldReceive('table')->andReturn($query);\n\n        return $query;\n    }\n\n    protected function makeStore($connection)\n    {\n        return new Akaunting\\Setting\\Drivers\\Database($connection);\n    }\n}\n"
  },
  {
    "path": "tests/unit/HelperTest.php",
    "content": "<?php\n\nuse Illuminate\\Container\\Container;\nuse Mockery as m;\n\nclass HelperTest extends PHPUnit_Framework_TestCase\n{\n    public static $functions;\n\n    public function setUp()\n    {\n        self::$functions = m::mock();\n\n        Container::setInstance(new Container());\n\n        $store = m::mock('Akaunting\\Setting\\Contracts\\Driver');\n\n        app()->bind('setting', function () use ($store) {\n            return $store;\n        });\n    }\n\n    /** @test */\n    public function helper_without_parameters_returns_store()\n    {\n        $this->assertInstanceOf('Akaunting\\Setting\\Contracts\\Driver', setting());\n    }\n\n    /** @test */\n    public function single_parameter_get_a_key_from_store()\n    {\n        app('setting')->shouldReceive('get')->with('foo', null)->once();\n\n        setting('foo');\n    }\n\n    public function two_parameters_return_a_default_value()\n    {\n        app('setting')->shouldReceive('get')->with('foo', 'bar')->once();\n\n        setting('foo', 'bar');\n    }\n\n    /** @test */\n    public function array_parameter_call_set_method_into_store()\n    {\n        app('setting')->shouldReceive('set')->with(['foo', 'bar'])->once();\n\n        setting(['foo', 'bar']);\n    }\n}\n"
  },
  {
    "path": "tests/unit/JsonDriverTest.php",
    "content": "<?php\n\nuse Mockery as m;\n\nclass JsonDriverTest extends PHPUnit_Framework_TestCase\n{\n    public function tearDown()\n    {\n        m::close();\n    }\n\n    protected function mockFilesystem()\n    {\n        return m::mock('Illuminate\\Filesystem\\Filesystem');\n    }\n\n    protected function makeStore($files, $path = 'fakepath')\n    {\n        return new Akaunting\\Setting\\Drivers\\Json($files, $path);\n    }\n\n    /**\n     * @test\n     * @expectedException InvalidArgumentException\n     */\n    public function throws_exception_when_file_not_writeable()\n    {\n        $files = $this->mockFilesystem();\n        $files->shouldReceive('exists')->once()->with('fakepath')->andReturn(true);\n        $files->shouldReceive('isWritable')->once()->with('fakepath')->andReturn(false);\n        $store = $this->makeStore($files);\n    }\n\n    /**\n     * @test\n     * @expectedException InvalidArgumentException\n     */\n    public function throws_exception_when_files_put_fails()\n    {\n        $files = $this->mockFilesystem();\n        $files->shouldReceive('exists')->once()->with('fakepath')->andReturn(false);\n        $files->shouldReceive('put')->once()->with('fakepath', '{}')->andReturn(false);\n        $store = $this->makeStore($files);\n    }\n\n    /**\n     * @test\n     * @expectedException RuntimeException\n     */\n    public function throws_exception_when_file_contains_invalid_json()\n    {\n        $files = $this->mockFilesystem();\n        $files->shouldReceive('exists')->once()->with('fakepath')->andReturn(true);\n        $files->shouldReceive('isWritable')->once()->with('fakepath')->andReturn(true);\n        $files->shouldReceive('get')->once()->with('fakepath')->andReturn('[[!1!11]');\n\n        $store = $this->makeStore($files);\n        $store->get('foo');\n    }\n}\n"
  }
]