Repository: optimistdigital/nova-settings
Branch: main
Commit: b0ad22ac34c8
Files: 58
Total size: 81.9 KB
Directory structure:
gitextract_ete0td8v/
├── .editorconfig
├── .gitattributes
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
├── config/
│ └── nova-settings.php
├── database/
│ └── migrations/
│ ├── 2019_08_13_000000_create_nova_settings_table.php
│ └── 2021_02_15_000000_update_nova_settings_value_column.php
├── dist/
│ ├── js/
│ │ ├── entry.js
│ │ └── entry.js.LICENSE.txt
│ └── mix-manifest.json
├── lang/
│ ├── ar.json
│ ├── de.json
│ ├── en.json
│ ├── es.json
│ ├── et.json
│ ├── fa.json
│ ├── fr.json
│ ├── it.json
│ ├── nl.json
│ ├── pt-BR.json
│ ├── ru.json
│ ├── sk.json
│ ├── tr.json
│ └── uz.json
├── package.json
├── phpunit.dusk.xml.dist
├── phpunit.xml.dist
├── resources/
│ └── js/
│ ├── entry.js
│ └── views/
│ └── Settings.vue
├── routes/
│ └── api.php
├── src/
│ ├── Http/
│ │ ├── Controllers/
│ │ │ └── SettingsController.php
│ │ └── Middleware/
│ │ ├── Authorize.php
│ │ └── SettingsPathExists.php
│ ├── Models/
│ │ └── Settings.php
│ ├── Nova/
│ │ └── Resources/
│ │ └── Settings.php
│ ├── NovaSettings.php
│ ├── NovaSettingsCacheStore.php
│ ├── NovaSettingsInMemoryStore.php
│ ├── NovaSettingsNoCacheStore.php
│ ├── NovaSettingsServiceProvider.php
│ ├── NovaSettingsStore.php
│ └── helpers.php
├── testbench.yaml
├── tests/
│ ├── Browser/
│ │ └── DetailTest.php
│ ├── DuskTestCase.php
│ ├── Feature/
│ │ ├── NavigationTest.php
│ │ ├── SettingsCastTest.php
│ │ ├── SettingsHelpersTest.php
│ │ ├── SettingsRetrieveTest.php
│ │ └── SettingsSaveTest.php
│ ├── IntegrationTestCase.php
│ └── bootstrap.php
└── webpack.mix.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[*.php]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
================================================
FILE: .gitattributes
================================================
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
# Ignore all test and documentation with "export-ignore".
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/phpunit.xml.dist export-ignore
/.scrutinizer.yml export-ignore
/.styleci.yml export-ignore
/tests export-ignore
/.editorconfig export-ignore
/docs export-ignore
================================================
FILE: .github/FUNDING.yml
================================================
github: outl1ne
================================================
FILE: .gitignore
================================================
/.idea
/vendor
/node_modules
composer.phar
composer.lock
phpunit.xml
.phpunit.result.cache
.DS_Store
Thumbs.db
.env.dusk
tests/Browser/console
tests/Browser/screenshots
auth.json
================================================
FILE: .prettierrc
================================================
{
"printWidth": 120,
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "avoid"
}
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [6.0.2] - 01-09-2025
### Added
- Added support for Markdown field preview
## [6.0.0] - 18-12-2024
### Added
- Nova 5 initial support
## [5.3.0] - 18-12-2024
### Added
- Added hugely improved caching store with options (thanks to [@manuel-watchenterprise](https://github.com/manuel-watchenterprise))
## [5.2.4] - 03-02-2024
### Added
- Added Spanish localization (thanks to [@dualklip](https://github.com/dualklip))
## [5.2.3] - 09-10-2023
### Fixed
- Fixed save button missing in Nova 4.28 (thanks to [@alancolant](https://github.com/alancolant))
## [5.2.2] - 09-10-2023
### Fixed
- Fixed casting of date and datetime objects when passing them into field ([see issue](https://github.com/outl1ne/nova-settings/issues/172))
## [5.2.1] - 10-08-2023
### Added
- Added Nova ->domain() support to routes (thanks to [@RonMelkhior](https://github.com/RonMelkhior))
### Changed
- Fixed null values not being persisted (thanks to [@Senexis](https://github.com/Senexis))
## [5.2.0] - 29-06-2023
### Added
- Added Nova 4.26 support (thanks to [@puzzledmonkey](https://github.com/puzzledmonkey))
## [5.1.0] - 20-03-2023
### Added
- Added Slovak language (thanks to [@wamesro](https://github.com/wamesro))
- Added resource-loaded and resource-updated Nova events
### Changed
- Allow encoding of JsonSerializable objects (thanks to [@miagg](https://github.com/miagg))
- Settings submenu is now hidden if there is only 1 menu element (thanks to [@johnpuddephatt](https://github.com/johnpuddephatt))
- Fixed image deletion when the image is inside a \Nova\Panel or \Eminiarts\Tabs (thanks to [@marttinnotta](https://github.com/marttinnotta))
- Updated packages
## [5.0.8] - 04-01-2023
### Changed
- Fixed `nova_get_settings()` not casting as expected
## [5.0.7] - 04-01-2023
### Added
- Added dusk identifier to update button (thanks to [@chrillep](https://github.com/chrillep))
### Changed
- Fixed `nova_get_settings()` not working as expected with default values
- Updated packages
## [5.0.6] - 21-10-2022
### Changed
- Added translations for French language (thanks to [@shaffe-fr](https://github.com/shaffe-fr))
## [5.0.5] - 08-09-2022
### Changed
- Fixed help text not rendering (thanks to [@mberatsanli](https://github.com/mberatsanli))
## [5.0.4] - 19-08-2022
### Changed
- Fixed nova-tabs support (thanks to [@Gertiozuni](https://github.com/Gertiozuni))
- Updated packages
## [5.0.3] - 19-07-2022
### Changed
- Fixed File and Image fields not deleting files from disk
## [5.0.2] - 24-05-2022
### Added
- Added Turkish translations (thanks to [@suleymanozev](https://github.com/suleymanozev))
### Changed
- Fixed not being redirected to login when accessing settings while unauthenticated (thanks to [@ianrobertsFF](https://github.com/ianrobertsFF))
## [5.0.1] - 14-05-2022
### Changed
- Fixed migrations (thanks to [@AndreasFurster](https://github.com/AndreasFurster))
## [5.0.0] - 13-05-2022
### Changed
- NB! Changed namespace from OptimistDigital to Outl1ne
- Allow redirections as a result of settings updates (thanks to [@ianrobertsFF](https://github.com/ianrobertsFF))
- Fixed sidebar subpages titles (thanks to [@faab007nl](https://github.com/faab007nl))
- Updated packages
## [4.0.4] - 29-04-2022
### Changed
- Removed loadViewsFrom() call from ServiceProvider
- Fixed memory cache not clearing after settings update
- Updated packages
## [4.0.3] - 25-04-2022
### Changed
- Changed `empty` check to `isset` when loading settings to allow negative but defined values
## [4.0.2] - 08-04-2022
### Changed
- Reworked routing logic
## [4.0.1] - 08-04-2022
### Changed
- Fixed page titles
## [4.0.0] - 08-04-2022
### Added
- Nova 4 support
- Fully compatible with light and dark modes
### Changed
- Dropped Laravel 7 and 8 support
- Dropped PHP 7.X support
- Dropped Nova 3 support
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2019 Outl1ne <info@optimistdigital.com>
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.
================================================
FILE: README.md
================================================
# Nova Settings
[](https://packagist.org/packages/outl1ne/nova-settings)
[](https://packagist.org/packages/outl1ne/nova-settings)
This [Laravel Nova](https://nova.laravel.com) package allows you to create custom settings in code (using Nova's native fields) and creates a UI for the users where the settings can be edited.
## Requirements
- `php: >=8.0`
- `laravel/nova: ^4.26`
## Features
- Settings fields management in code
- UI for editing settings
- Helpers for accessing settings
- Rule validation support
## Screenshots

## Installation
Install the package in a Laravel Nova project via Composer and run migrations:
```bash
# Install nova-settings
composer require outl1ne/nova-settings
# Run migrations
php artisan migrate
```
Register the tool with Nova in the `tools()` method of the `NovaServiceProvider`:
```php
// in app/Providers/NovaServiceProvider.php
public function tools()
{
return [
// ...
new \Outl1ne\NovaSettings\NovaSettings
];
}
```
## Usage
### Registering fields
Define the fields in your `NovaServiceProvider`'s `boot()` function by calling `NovaSettings::addSettingsFields()`.
```php
// Using an array
\Outl1ne\NovaSettings\NovaSettings::addSettingsFields([
Text::make('Some setting', 'some_setting'),
Number::make('A number', 'a_number'),
]);
// OR
// Using a callable
\Outl1ne\NovaSettings\NovaSettings::addSettingsFields(function() {
return [
Text::make('Some setting', 'some_setting'),
Number::make('A number', 'a_number'),
];
});
```
#### Registering field panels
```php
// Using an array
\Outl1ne\NovaSettings\NovaSettings::addSettingsFields([
Panel::make('Panel Title', [
Text::make('Some setting', 'some_setting'),
Number::make('A number', 'a_number'),
]),
]);
```
### Casts
If you want the value of the setting to be formatted before it's returned, pass an array similar to `Eloquent`'s `$casts` property as the second parameter.
```php
\Outl1ne\NovaSettings\NovaSettings::addSettingsFields([
// ... fields
], [
'some_boolean_value' => 'boolean',
'some_float' => 'float',
'some_collection' => 'collection',
// ...
]);
```
### Subpages
Add a settings page name as a third argument to list those settings in a custom subpage.
```php
\Outl1ne\NovaSettings\NovaSettings::addSettingsFields([
Text::make('Some setting', 'some_setting'),
Number::make('A number', 'a_number'),
], [], 'Subpage');
```
If you leave the custom name empty, the field(s) will be listed under "General".
To translate the page name, publish the translations and add a new key `novaSettings.$subpage` to the respective translations file, where `$subpage` is the name of the page (full lowercase, slugified).
### Authorization
#### Show/hide all settings
If you want to hide the whole `Settings` area from the sidebar, you can authorize the `NovaSettings` tool like so:
```php
public function tools(): array
{
return [
NovaSettings::make()->canSee(fn () => user()->isAdmin()),
];
}
```
#### Show/hide specific setting fields
If you want to hide only some settings, you can use `->canSee(fn () => ...)` per field. Like so:
```php
Text::make('A text field')
->canSee(fn () => user()->isAdmin()),
```
### Helper functions
#### nova_get_settings(\$keys = null, \$defaults = [])
Call `nova_get_settings()` to get all the settings formated as a regular array. Additionally, you can pass a `key => value` array as a second argument: `nova_get_settings(['some_key], ['some_key' => 'default_value'])`.
#### nova_get_setting(\$key, \$default = null)
To get a single setting's value, call `nova_get_setting('some_setting_key')`. It will return either a value or null if there's no setting with such key.
You can also pass default value as a second argument `nova_get_setting('some_setting_key', 'default_value')`, which will be returned, if no setting was found with given key.
#### nova_set_setting_value(\$key, \$value = null)
Sets a setting value for the given key.
## Configuration
The config file can be published using the following command:
```bash
php artisan vendor:publish --provider="Outl1ne\NovaSettings\NovaSettingsServiceProvider" --tag="config"
```
| Name | Type | Default | Description |
|-----------------------|---------|-------------------|--------------------------------------------------------------------------------------------------|
| `base_path` | String | `nova-settings` | URL path of settings page. |
| `reload_page_on_save` | Boolean | false | Reload the entire page on save. Useful when updating any Nova UI related settings. |
| `models.settings` | Model | `Settings::class` | Optionally override the Settings model. |
| `cache` | String | `:memory:` | Cache store name to use that cache, ":memory:" for singleton class, or null to turn off caching. |
The migration can also be published and overwritten using:
```bash
php artisan vendor:publish --provider="Outl1ne\NovaSettings\NovaSettingsServiceProvider" --tag="migrations"
```
## Localization
The translation file(s) can be published by using the following command:
```bash
php artisan vendor:publish --provider="Outl1ne\NovaSettings\NovaSettingsServiceProvider" --tag="translations"
```
You can add your translations to `resources/lang/vendor/nova-settings/` by creating a new translations file with the locale name (ie `et.json`) and copying the JSON from the existing `en.json`.
## Credits
- [Tarvo Reinpalu](https://github.com/Tarpsvo)
## License
Nova Settings is open-sourced software licensed under the [MIT license](LICENSE.md).
================================================
FILE: composer.json
================================================
{
"name": "outl1ne/nova-settings",
"description": "A Laravel Nova tool for editing custom settings using native Nova fields.",
"keywords": [
"laravel",
"nova",
"settings"
],
"authors": [
{
"name": "Tarvo Reinpalu",
"email": "tarvo@outl1ne.com",
"role": "Developer"
},
{
"name": "Outl1ne",
"email": "info@outl1ne.com",
"role": "Maintainer"
}
],
"license": "MIT",
"require": {
"php": ">=8.1",
"laravel/nova": "^5.0",
"outl1ne/nova-translations-loader": "^5.0"
},
"require-dev": {
"laravel/nova-devtool": "^1.0",
"nunomaduro/collision": "^7.8",
"orchestra/testbench": "^8.30|^9.8"
},
"autoload": {
"psr-4": {
"Outl1ne\\NovaSettings\\": "src/"
},
"files": [
"./src/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Outl1ne\\NovaSettings\\Tests\\": "tests"
}
},
"extra": {
"laravel": {
"providers": [
"Outl1ne\\NovaSettings\\NovaSettingsServiceProvider"
]
}
},
"config": {
"sort-packages": true,
"allow-plugins": {
"php-http/discovery": true
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"repositories": [
{
"type": "composer",
"url": "https://nova.laravel.com"
}
],
"scripts": {
"dusk:prepare": [
"./vendor/bin/dusk-updater detect --auto-update"
],
"dusk:assets": [
"npm ci",
"npm run prod",
"./vendor/bin/testbench-dusk nova:publish"
],
"dusk:test": [
"./vendor/bin/phpunit -c phpunit.dusk.xml.dist"
]
}
}
================================================
FILE: config/nova-settings.php
================================================
<?php
return [
/**
* Set a name for the settings table
*/
'table' => 'nova_settings',
/**
* URL path of settings page
*/
'base_path' => 'nova-settings',
/**
* Reload the entire page on save. Useful when updating any Nova UI related settings.
*/
'reload_page_on_save' => false,
/**
* We need to know which eloquent model should be used to retrieve your permissions.
* Of course, it is often just the default model but you may use whatever you like.
*
* The model you want to use as a model needs to extend the original model.
*/
'models' => [
'settings' => \Outl1ne\NovaSettings\Models\Settings::class,
],
/**
* Show the sidebar menu
*/
'show_in_sidebar' => true,
/*
|--------------------------------------------------------------------------
| Cache settings
|--------------------------------------------------------------------------
|
| Here you may specify which of the cache connection should be used to
| cache the settings. `:memory:` is the default which is a simple
| in-memory cache through a singleton service class property.
| `null` will disable caching.
|
*/
'cache' => [
'store' => env('NOVA_SETTINGS_CACHE_DRIVER', ':memory:'),
'prefix' => 'nova-settings:',
],
];
================================================
FILE: database/migrations/2019_08_13_000000_create_nova_settings_table.php
================================================
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Outl1ne\NovaSettings\NovaSettings;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// Settings table
Schema::create(NovaSettings::getSettingsTableName(), function (Blueprint $table) {
$table->string('key')->unique()->primary();
$table->text('value')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists(NovaSettings::getSettingsTableName());
}
};
================================================
FILE: database/migrations/2021_02_15_000000_update_nova_settings_value_column.php
================================================
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Outl1ne\NovaSettings\NovaSettings;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// TODO Remove in next major release
// Legacy support
// Convert value column to text if needed as the 'value' column was previously a varchar
Schema::table(NovaSettings::getSettingsTableName(), function (Blueprint $table) {
$table->text('value')->nullable()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// No down because previous migration was also modified
}
};
================================================
FILE: dist/js/entry.js
================================================
/*! For license information please see entry.js.LICENSE.txt */
(()=>{var t={262:(t,e)=>{"use strict";e.A=(t,e)=>{const r=t.__vccOpts||t;for(const[t,n]of e)r[t]=n;return r}},189:(t,e,r)=>{"use strict";r.d(e,{A:()=>v});const n=Vue;var o={key:0,class:"flex items-center"},a={key:1,class:"bg-white dark:bg-gray-800 rounded-lg shadow p-3"},i={class:"flex flex-col justify-center align-center"},c={class:"w-3/4 py-4 text-center"},s={class:"text-90"};const u=LaravelNova;function l(t){return l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},l(t)}function f(){f=function(){return e};var t,e={},r=Object.prototype,n=r.hasOwnProperty,o=Object.defineProperty||function(t,e,r){t[e]=r.value},a="function"==typeof Symbol?Symbol:{},i=a.iterator||"@@iterator",c=a.asyncIterator||"@@asyncIterator",s=a.toStringTag||"@@toStringTag";function u(t,e,r){return Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}),t[e]}try{u({},"")}catch(t){u=function(t,e,r){return t[e]=r}}function p(t,e,r,n){var a=e&&e.prototype instanceof w?e:w,i=Object.create(a.prototype),c=new I(n||[]);return o(i,"_invoke",{value:O(t,r,c)}),i}function d(t,e,r){try{return{type:"normal",arg:t.call(e,r)}}catch(t){return{type:"throw",arg:t}}}e.wrap=p;var h="suspendedStart",v="suspendedYield",g="executing",y="completed",m={};function w(){}function b(){}function x(){}var E={};u(E,i,(function(){return this}));var k=Object.getPrototypeOf,L=k&&k(k(F([])));L&&L!==r&&n.call(L,i)&&(E=L);var _=x.prototype=w.prototype=Object.create(E);function N(t){["next","throw","return"].forEach((function(e){u(t,e,(function(t){return this._invoke(e,t)}))}))}function S(t,e){function r(o,a,i,c){var s=d(t[o],t,a);if("throw"!==s.type){var u=s.arg,f=u.value;return f&&"object"==l(f)&&n.call(f,"__await")?e.resolve(f.__await).then((function(t){r("next",t,i,c)}),(function(t){r("throw",t,i,c)})):e.resolve(f).then((function(t){u.value=t,i(u)}),(function(t){return r("throw",t,i,c)}))}c(s.arg)}var a;o(this,"_invoke",{value:function(t,n){function o(){return new e((function(e,o){r(t,n,e,o)}))}return a=a?a.then(o,o):o()}})}function O(e,r,n){var o=h;return function(a,i){if(o===g)throw Error("Generator is already running");if(o===y){if("throw"===a)throw i;return{value:t,done:!0}}for(n.method=a,n.arg=i;;){var c=n.delegate;if(c){var s=T(c,n);if(s){if(s===m)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if(o===h)throw o=y,n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);o=g;var u=d(e,r,n);if("normal"===u.type){if(o=n.done?y:v,u.arg===m)continue;return{value:u.arg,done:n.done}}"throw"===u.type&&(o=y,n.method="throw",n.arg=u.arg)}}}function T(e,r){var n=r.method,o=e.iterator[n];if(o===t)return r.delegate=null,"throw"===n&&e.iterator.return&&(r.method="return",r.arg=t,T(e,r),"throw"===r.method)||"return"!==n&&(r.method="throw",r.arg=new TypeError("The iterator does not provide a '"+n+"' method")),m;var a=d(o,e.iterator,r.arg);if("throw"===a.type)return r.method="throw",r.arg=a.arg,r.delegate=null,m;var i=a.arg;return i?i.done?(r[e.resultName]=i.value,r.next=e.nextLoc,"return"!==r.method&&(r.method="next",r.arg=t),r.delegate=null,m):i:(r.method="throw",r.arg=new TypeError("iterator result is not an object"),r.delegate=null,m)}function j(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function B(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function I(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(j,this),this.reset(!0)}function F(e){if(e||""===e){var r=e[i];if(r)return r.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var o=-1,a=function r(){for(;++o<e.length;)if(n.call(e,o))return r.value=e[o],r.done=!1,r;return r.value=t,r.done=!0,r};return a.next=a}}throw new TypeError(l(e)+" is not iterable")}return b.prototype=x,o(_,"constructor",{value:x,configurable:!0}),o(x,"constructor",{value:b,configurable:!0}),b.displayName=u(x,s,"GeneratorFunction"),e.isGeneratorFunction=function(t){var e="function"==typeof t&&t.constructor;return!!e&&(e===b||"GeneratorFunction"===(e.displayName||e.name))},e.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,x):(t.__proto__=x,u(t,s,"GeneratorFunction")),t.prototype=Object.create(_),t},e.awrap=function(t){return{__await:t}},N(S.prototype),u(S.prototype,c,(function(){return this})),e.AsyncIterator=S,e.async=function(t,r,n,o,a){void 0===a&&(a=Promise);var i=new S(p(t,r,n,o),a);return e.isGeneratorFunction(r)?i:i.next().then((function(t){return t.done?t.value:i.next()}))},N(_),u(_,s,"Generator"),u(_,i,(function(){return this})),u(_,"toString",(function(){return"[object Generator]"})),e.keys=function(t){var e=Object(t),r=[];for(var n in e)r.push(n);return r.reverse(),function t(){for(;r.length;){var n=r.pop();if(n in e)return t.value=n,t.done=!1,t}return t.done=!0,t}},e.values=F,I.prototype={constructor:I,reset:function(e){if(this.prev=0,this.next=0,this.sent=this._sent=t,this.done=!1,this.delegate=null,this.method="next",this.arg=t,this.tryEntries.forEach(B),!e)for(var r in this)"t"===r.charAt(0)&&n.call(this,r)&&!isNaN(+r.slice(1))&&(this[r]=t)},stop:function(){this.done=!0;var t=this.tryEntries[0].completion;if("throw"===t.type)throw t.arg;return this.rval},dispatchException:function(e){if(this.done)throw e;var r=this;function o(n,o){return c.type="throw",c.arg=e,r.next=n,o&&(r.method="next",r.arg=t),!!o}for(var a=this.tryEntries.length-1;a>=0;--a){var i=this.tryEntries[a],c=i.completion;if("root"===i.tryLoc)return o("end");if(i.tryLoc<=this.prev){var s=n.call(i,"catchLoc"),u=n.call(i,"finallyLoc");if(s&&u){if(this.prev<i.catchLoc)return o(i.catchLoc,!0);if(this.prev<i.finallyLoc)return o(i.finallyLoc)}else if(s){if(this.prev<i.catchLoc)return o(i.catchLoc,!0)}else{if(!u)throw Error("try statement without catch or finally");if(this.prev<i.finallyLoc)return o(i.finallyLoc)}}}},abrupt:function(t,e){for(var r=this.tryEntries.length-1;r>=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev<o.finallyLoc){var a=o;break}}a&&("break"===t||"continue"===t)&&a.tryLoc<=e&&e<=a.finallyLoc&&(a=null);var i=a?a.completion:{};return i.type=t,i.arg=e,a?(this.method="next",this.next=a.finallyLoc,m):this.complete(i)},complete:function(t,e){if("throw"===t.type)throw t.arg;return"break"===t.type||"continue"===t.type?this.next=t.arg:"return"===t.type?(this.rval=this.arg=t.arg,this.method="return",this.next="end"):"normal"===t.type&&e&&(this.next=e),m},finish:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),B(r),m}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;B(r)}return o}}throw Error("illegal catch attempt")},delegateYield:function(e,r,n){return this.delegate={iterator:F(e),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=t),m}},e}function p(t,e,r,n,o,a,i){try{var c=t[a](i),s=c.value}catch(t){return void r(t)}c.done?e(s):Promise.resolve(s).then(n,o)}function d(t){return function(){var e=this,r=arguments;return new Promise((function(n,o){var a=t.apply(e,r);function i(t){p(a,n,o,i,c,"next",t)}function c(t){p(a,n,o,i,c,"throw",t)}i(void 0)}))}}const h={components:{Button:LaravelNovaUi.Button},data:function(){return{pageId:!1,loading:!1,isUpdating:!1,fields:[],panels:[],authorizations:[],validationErrors:new u.Errors}},created:function(){var t=this;return d(f().mark((function e(){return f().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:t.pageId=t.$page.props.pageId||"general",t.getFields();case 2:case"end":return e.stop()}}),e)})))()},methods:{getFields:function(){var t=this;return d(f().mark((function e(){var r,n,o,a,i,c,s;return f().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return t.loading=!0,t.fields=[],r={editing:!0,editMode:"update"},t.pageId&&(r.path=t.pageId),e.next=6,Nova.request().get("/nova-vendor/nova-settings/settings",{params:r}).catch((function(t){t.response.status}));case 6:n=e.sent,o=n.data,a=o.fields,i=o.panels,c=o.authorizations,t.fields=a,t.panels=i,t.authorizations=c,t.loading=!1,s=t.isUpdating?"resource-updated":"resource-loaded",Nova.$emit(s,{resourceName:"nova-settings"});case 17:case"end":return e.stop()}}),e)})))()},update:function(){var t=this;return d(f().mark((function e(){var r;return f().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,t.isUpdating=!0,e.next=4,t.updateRequest();case 4:if(!(r=e.sent)||!r.data){e.next=14;break}if(!0!==r.data.reload){e.next=11;break}return location.reload(),e.abrupt("return");case 11:if(!(r.data.redirect&&r.data.redirect.length>0)){e.next=14;break}return location.replace(r.data.redirect),e.abrupt("return");case 14:return Nova.success(t.__("novaSettings.settingsSuccessToast")),e.next=17,t.getFields();case 17:t.isUpdating=!1,t.validationErrors=new u.Errors,e.next=26;break;case 21:e.prev=21,e.t0=e.catch(0),console.error(e.t0),t.isUpdating=!1,e.t0&&e.t0.response&&422==e.t0.response.status&&(t.validationErrors=new u.Errors(e.t0.response.data.errors),Nova.error(t.__("There was a problem submitting the form.")));case 26:case"end":return e.stop()}}),e,null,[[0,21]])})))()},updateRequest:function(){return Nova.request().post("/nova-vendor/nova-settings/settings",this.formData)}},computed:{formData:function(){var t=new FormData;return this.fields.forEach((function(e){return e.fill(t)})),t.append("_method","POST"),this.pageId&&t.append("path",this.pageId),t},panelsWithFields:function(){var t=this;return this.panels.map((function(e){return{name:e.name,component:e.component,helpText:e.helpText,fields:t.fields.filter((function(t){return t.panel===e.name})),showTitle:e.showTitle}}))}}};const v=(0,r(262).A)(h,[["render",function(t,e,r,u,l,f){var p=(0,n.resolveComponent)("Head"),d=(0,n.resolveComponent)("Button"),h=(0,n.resolveComponent)("LoadingView");return(0,n.openBlock)(),(0,n.createBlock)(h,{loading:l.loading,key:l.pageId},{default:(0,n.withCtx)((function(){return[(0,n.createVNode)(p,{title:t.__("novaSettings.navigationItemTitle")+("general"!==l.pageId?" (".concat(l.pageId,")"):"")},null,8,["title"]),l.fields&&l.fields.length?((0,n.openBlock)(),(0,n.createElementBlock)("form",{key:0,onSubmit:e[0]||(e[0]=(0,n.withModifiers)((function(){return f.update&&f.update.apply(f,arguments)}),["prevent"])),autocomplete:"off",dusk:"nova-settings-form"},[((0,n.openBlock)(!0),(0,n.createElementBlock)(n.Fragment,null,(0,n.renderList)(f.panelsWithFields,(function(t){return(0,n.openBlock)(),(0,n.createBlock)((0,n.resolveDynamicComponent)("form-"+t.component),{key:t.name,panel:t,name:t.name,fields:t.fields,"resource-name":"nova-settings","resource-id":l.pageId,mode:"form",class:"mb-6","validation-errors":l.validationErrors,"show-help-text":!0},null,8,["panel","name","fields","resource-id","validation-errors"])})),128)),l.authorizations.authorizedToUpdate?((0,n.openBlock)(),(0,n.createElementBlock)("div",o,[(0,n.createVNode)(d,{dusk:"update-button",type:"submit",class:"ml-auto",disabled:l.isUpdating,loading:l.isUpdating},{default:(0,n.withCtx)((function(){return[(0,n.createTextVNode)((0,n.toDisplayString)(t.__("novaSettings.saveButtonText")),1)]})),_:1},8,["disabled","loading"])])):(0,n.createCommentVNode)("",!0)],32)):((0,n.openBlock)(),(0,n.createElementBlock)("div",a,[(0,n.createElementVNode)("div",i,[(0,n.createElementVNode)("div",c,[(0,n.createElementVNode)("p",s,(0,n.toDisplayString)(t.__("novaSettings.noSettingsFieldsText")),1)])])]))]})),_:1},8,["loading"])}]])}},e={};function r(n){var o=e[n];if(void 0!==o)return o.exports;var a=e[n]={exports:{}};return t[n](a,a.exports,r),a.exports}r.d=(t,e)=>{for(var n in e)r.o(e,n)&&!r.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),Nova.booting((function(t,e,n){Nova.inertia("NovaSettings",r(189).A)}))})();
================================================
FILE: dist/js/entry.js.LICENSE.txt
================================================
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
================================================
FILE: dist/mix-manifest.json
================================================
{
"/js/entry.js": "/js/entry.js"
}
================================================
FILE: lang/ar.json
================================================
{
"novaSettings.navigationItemTitle": "الإعدادات",
"novaSettings.saveButtonText": "حفظ الإعدادات",
"novaSettings.noSettingsFieldsText": "لم يتم اضافة حقول للإعدادات.",
"novaSettings.settingsSuccessToast": "تم حفظ الإعدادات",
"novaSettings.general": "الإعدادات العامة"
}
================================================
FILE: lang/de.json
================================================
{
"novaSettings.navigationItemTitle": "Einstellungen",
"novaSettings.saveButtonText": "Einstellungen speichern",
"novaSettings.noSettingsFieldsText": "Es wurden keine Einstellungs-Felder definiert.",
"novaSettings.settingsSuccessToast": "Einstellungen erfolgreich geändert."
}
================================================
FILE: lang/en.json
================================================
{
"novaSettings.navigationItemTitle": "Settings",
"novaSettings.saveButtonText": "Save settings",
"novaSettings.noSettingsFieldsText": "No settings fields have been defined.",
"novaSettings.settingsSuccessToast": "Settings successfully updated!",
"novaSettings.general": "General"
}
================================================
FILE: lang/es.json
================================================
{
"novaSettings.navigationItemTitle": "Configuración",
"novaSettings.saveButtonText": "Guardar configuración",
"novaSettings.noSettingsFieldsText": "No se ha definido ningún campo de configuración.",
"novaSettings.settingsSuccessToast": "¡La configuración se ha actualizado correctamente!",
"novaSettings.general": "General"
}
================================================
FILE: lang/et.json
================================================
{
"novaSettings.navigationItemTitle": "Seaded",
"novaSettings.saveButtonText": "Salvesta seaded",
"novaSettings.noSettingsFieldsText": "Ühtegi seadevälja ei ole defineeritud.",
"novaSettings.settingsSuccessToast": "Seaded uuendatud!",
"novaSettings.general": "Üldine"
}
================================================
FILE: lang/fa.json
================================================
{
"novaSettings.navigationItemTitle": "تنظیمات",
"novaSettings.saveButtonText": "ذخیره تنظیمات",
"novaSettings.noSettingsFieldsText": "هیچ تنظیمی تعریف نشده است.",
"novaSettings.settingsSuccessToast": "تنظیمات ذخیره شد.",
"novaSettings.general": "عمومی"
}
================================================
FILE: lang/fr.json
================================================
{
"novaSettings.navigationItemTitle": "Paramètres",
"novaSettings.saveButtonText": "Enregistrer paramètres",
"novaSettings.noSettingsFieldsText": "Aucun champ paramètres n'a été défini.",
"novaSettings.settingsSuccessToast": "Paramètres mis à jour !",
"novaSettings.general": "Paramètres généraux"
}
================================================
FILE: lang/it.json
================================================
{
"novaSettings.navigationItemTitle": "Impostazioni",
"novaSettings.saveButtonText": "Salva impostazioni",
"novaSettings.noSettingsFieldsText": "Nessun campo Impostazioni è stato definito.",
"novaSettings.settingsSuccessToast": "Impostazioni aggiornate con successo",
"novaSettings.general": "Generale"
}
================================================
FILE: lang/nl.json
================================================
{
"novaSettings.navigationItemTitle": "Instellingen",
"novaSettings.saveButtonText": "Instellingen opslaan",
"novaSettings.noSettingsFieldsText": "Geen veld gedefineerd.",
"novaSettings.settingsSuccessToast": "Instellingen succesvol geupdatet",
"novaSettings.general": "Algemeen"
}
================================================
FILE: lang/pt-BR.json
================================================
{
"novaSettings.navigationItemTitle": "Configurações",
"novaSettings.saveButtonText": "Salvar configurações",
"novaSettings.noSettingsFieldsText": "Nenhum campo de configuração foi definido.",
"novaSettings.settingsSuccessToast": "Configurações atualizadas com sucesso",
"novaSettings.general": "Geral"
}
================================================
FILE: lang/ru.json
================================================
{
"novaSettings.navigationItemTitle": "Настройки",
"novaSettings.saveButtonText": "Сохранить",
"novaSettings.noSettingsFieldsText": "Поля настроек не определены.",
"novaSettings.settingsSuccessToast": "Настройки успешно обновлены",
"novaSettings.general": "Основные"
}
================================================
FILE: lang/sk.json
================================================
{
"novaSettings.navigationItemTitle": "Nastavenia",
"novaSettings.saveButtonText": "Uložiť nastavenia",
"novaSettings.noSettingsFieldsText": "Neboli definované žiadne polia nastavení.",
"novaSettings.settingsSuccessToast": "Nastavenia boli úspešne aktualizované!",
"novaSettings.general": "Všeobecné"
}
================================================
FILE: lang/tr.json
================================================
{
"novaSettings.navigationItemTitle": "Ayarlar",
"novaSettings.saveButtonText": "Ayarları Kaydet",
"novaSettings.noSettingsFieldsText": "Henüz bir ayar alanı tanımlamadınız.",
"novaSettings.settingsSuccessToast": "Ayarlar başarıyla güncellendi!",
"novaSettings.general": "Genel"
}
================================================
FILE: lang/uz.json
================================================
{
"novaSettings.navigationItemTitle": "Sozlamalar",
"novaSettings.saveButtonText": "Saqlash",
"novaSettings.noSettingsFieldsText": "Sozlamalar maydonlari belgilanmagan.",
"novaSettings.settingsSuccessToast": "Sozlamalar muvaffaqiyatli saqlandi",
"novaSettings.general": "Umumiy"
}
================================================
FILE: package.json
================================================
{
"private": true,
"scripts": {
"dev": "mix",
"watch": "mix watch",
"hot": "mix watch --hot",
"prod": "mix --production",
"format": "prettier --write 'resources/**/*.{css,js,vue}'"
},
"devDependencies": {
"laravel-mix": "^6.0.49",
"@vue/babel-plugin-jsx": "^1.2.5",
"cross-env": "^7.0.3",
"laravel-nova-devtool": "file:vendor/laravel/nova-devtool",
"prettier": "^3.4.2",
"resolve-url-loader": "^5.0.0",
"sass": "^1.83.0",
"sass-loader": "^16.0.4",
"terser-webpack-plugin": "^5.3.11",
"vue-loader": "^16.8.3",
"vue-template-compiler": "^2.7.16",
"vuex": "^4.1.0"
},
"dependencies": {
"vue": "^3.5.13"
}
}
================================================
FILE: phpunit.dusk.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" bootstrap="tests/bootstrap.php" colors="true">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Browser Test Suite">
<directory suffix="Test.php">./tests/Browser</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_URL" value="http://127.0.0.1:8085"/>
<env name="APP_ENV" value="testing"/>
<env name="APP_KEY" value="base64:+Yk3U4u5A5sSnN6kUr5b38I2LE/SgNxwov5XX5FvSW0="/>
<env name="APP_DEBUG" value="true"/>
<env name="DUSK_TIMEZONE" value="Europe/Tallinn"/>
<env name="DUSK_WIDTH" value="1920"/>
<env name="DUSK_HEIGHT" value="1080"/>
</php>
</phpunit>
================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" backupStaticAttributes="false" colors="true" verbose="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage>
<include>
<directory suffix=".php">src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Test Suite">
<directory>./tests/Feature</directory>
</testsuite>
</testsuites>
</phpunit>
================================================
FILE: resources/js/entry.js
================================================
Nova.booting((Vue, router, store) => {
Nova.inertia('NovaSettings', require('./views/Settings').default);
});
================================================
FILE: resources/js/views/Settings.vue
================================================
<template>
<LoadingView :loading="loading" :key="pageId">
<Head :title="__('novaSettings.navigationItemTitle') + (pageId !== 'general' ? ` (${pageId})` : '')" />
<form v-if="fields && fields.length" @submit.prevent="update" autocomplete="off" dusk="nova-settings-form">
<template v-for="panel in panelsWithFields" :key="panel.name">
<component
:is="`form-` + panel.component"
:panel="panel"
:name="panel.name"
:fields="panel.fields"
:resource-name="'nova-settings'"
:resource-id="pageId"
mode="form"
class="mb-6"
:validation-errors="validationErrors"
:show-help-text="true"
/>
</template>
<!-- Update Button -->
<div class="flex items-center" v-if="authorizations.authorizedToUpdate">
<Button dusk="update-button" type="submit" class="ml-auto" :disabled="isUpdating" :loading="isUpdating">
{{ __('novaSettings.saveButtonText') }}
</Button>
</div>
</form>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-3" v-else>
<div class="flex flex-col justify-center align-center">
<div class="w-3/4 py-4 text-center">
<p class="text-90">{{ __('novaSettings.noSettingsFieldsText') }}</p>
</div>
</div>
</div>
</LoadingView>
</template>
<script>
import { Errors } from 'laravel-nova';
import { Button } from 'laravel-nova-ui';
export default {
components: { Button },
data() {
return {
pageId: false,
loading: false,
isUpdating: false,
fields: [],
panels: [],
authorizations: [],
validationErrors: new Errors(),
};
},
async created() {
this.pageId = this.$page.props.pageId || 'general';
this.getFields();
},
methods: {
async getFields() {
this.loading = true;
this.fields = [];
const params = { editing: true, editMode: 'update' };
if (this.pageId) params.path = this.pageId;
const {
data: { fields, panels, authorizations },
} = await Nova.request()
.get('/nova-vendor/nova-settings/settings', { params })
.catch(error => {
if (error.response.status == 404) {
// this.$router.push({ name: '404' });
return;
}
});
this.fields = fields;
this.panels = panels;
this.authorizations = authorizations;
this.loading = false;
// Dispatch event
const eventName = this.isUpdating ? 'resource-updated' : 'resource-loaded';
Nova.$emit(eventName, {
resourceName: 'nova-settings',
});
},
async update() {
try {
this.isUpdating = true;
const response = await this.updateRequest();
if (response && response.data) {
if (response.data.reload === true) {
location.reload();
return;
} else if (response.data.redirect && response.data.redirect.length > 0) {
location.replace(response.data.redirect);
return;
}
}
Nova.success(this.__('novaSettings.settingsSuccessToast'));
// Reset the form by refetching the fields
await this.getFields();
this.isUpdating = false;
this.validationErrors = new Errors();
} catch (error) {
console.error(error);
this.isUpdating = false;
if (error && error.response && error.response.status == 422) {
this.validationErrors = new Errors(error.response.data.errors);
Nova.error(this.__('There was a problem submitting the form.'));
}
}
},
updateRequest() {
return Nova.request().post('/nova-vendor/nova-settings/settings', this.formData);
},
},
computed: {
formData() {
const formData = new FormData();
this.fields.forEach(field => field.fill(formData));
formData.append('_method', 'POST');
if (this.pageId) formData.append('path', this.pageId);
return formData;
},
panelsWithFields() {
return this.panels.map(panel => {
return {
name: panel.name,
component: panel.component,
helpText: panel.helpText,
fields: this.fields.filter(field => field.panel === panel.name),
showTitle: panel.showTitle,
};
});
},
},
};
</script>
================================================
FILE: routes/api.php
================================================
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Tool API Routes
|--------------------------------------------------------------------------
|
| Here is where you may register API routes for your tool. These routes
| are loaded by the ServiceProvider of your tool. They are protected
| by your tool's "Authorize" middleware by default. Now, go build!
|
*/
Route::namespace('\Outl1ne\NovaSettings\Http\Controllers')->group(function () {
Route::prefix('nova-vendor/nova-settings')->group(function () {
Route::get('/settings', 'SettingsController@get')->name('nova-settings.get');
Route::post('/settings', 'SettingsController@save')->name('nova-settings.save');
});
Route::delete('/nova-api/nova-settings/{path}/field/{fieldName}', 'SettingsController@deleteImage');
Route::post('/nova-api/nova-settings/{path}/field/{attribute}/preview', 'SettingsController@fieldPreview')->name('nova-settings.field-preview');
});
================================================
FILE: src/Http/Controllers/SettingsController.php
================================================
<?php
namespace Outl1ne\NovaSettings\Http\Controllers;
use Laravel\Nova\Panel;
use Illuminate\Http\Request;
use Laravel\Nova\ResolvesFields;
use Illuminate\Routing\Controller;
use Laravel\Nova\Contracts\Resolvable;
use Outl1ne\NovaSettings\NovaSettings;
use Illuminate\Support\Facades\Storage;
use Laravel\Nova\Fields\FieldCollection;
use Illuminate\Support\Facades\Validator;
use Laravel\Nova\Http\Requests\NovaRequest;
use Illuminate\Http\Resources\ConditionallyLoadsAttributes;
class SettingsController extends Controller
{
use ResolvesFields, ConditionallyLoadsAttributes;
public function get(Request $request)
{
if (!NovaSettings::canSeeSettings()) return $this->unauthorized();
$path = $request->get('path', 'general');
$label = NovaSettings::getPageName($path);
$fields = $this->assignToPanels($label, $this->availableFields($path));
$panels = $this->panelsWithDefaultLabel($label, app(NovaRequest::class));
$addResolveCallback = function (&$field) {
if (!empty($field->attribute)) {
$setting = NovaSettings::getSettingsModel()::firstOrNew(['key' => $field->attribute]);
$fakeResource = $this->makeFakeResource($field->attribute, isset($setting) ? $setting->value : '');
$field->resolve($fakeResource);
}
if (!empty($field->meta['fields'])) {
foreach ($field->meta['fields'] as $_field) {
$setting = NovaSettings::getSettingsModel()::where('key', $_field->attribute)->first();
$fakeResource = $this->makeFakeResource($_field->attribute, isset($setting) ? $setting->value : null);
$_field->resolve($fakeResource);
}
}
};
$fields->each(function (&$field) use ($addResolveCallback) {
$addResolveCallback($field);
});
return response()->json([
'panels' => $panels,
'fields' => $fields,
'authorizations' => NovaSettings::getAuthorizations(),
], 200);
}
public function save(NovaRequest $request)
{
if (!NovaSettings::getAuthorizations('authorizedToUpdate')) return $this->unauthorized();
$fields = $this->availableFields($request->get('path', 'general'));
// NovaDependencyContainer support
$fields = $fields->map(function ($field) {
if (!empty($field->attribute)) return $field;
if (!empty($field->meta['fields'])) return $field->meta['fields'];
return null;
})->filter()->flatten();
$rules = [];
foreach ($fields as $field) {
$fakeResource = $this->makeFakeResource($field->attribute, nova_get_setting($field->attribute));
$field->resolve($fakeResource, $field->attribute); // For nova-translatable support
$rules = array_merge($rules, $field->getUpdateRules($request));
}
Validator::make($request->all(), $rules)->validate();
$fields->whereInstanceOf(Resolvable::class)->each(function ($field) use ($request) {
if (empty($field->attribute)) return;
if ($field->isReadonly(app(NovaRequest::class))) return;
$settingsClass = NovaSettings::getSettingsModel();
// For nova-translatable support
if (!empty($field->meta['translatable']['original_attribute'])) $field->attribute = $field->meta['translatable']['original_attribute'];
$existingRow = $settingsClass::where('key', $field->attribute)->first();
$tempResource = new \Laravel\Nova\Support\Fluent;
$field->fill($request, $tempResource);
if (!array_key_exists($field->attribute, $tempResource->getAttributes())) return;
if (isset($existingRow)) {
$existingRow->value = $tempResource->{$field->attribute};
$existingRow->save();
} else {
$newRow = new $settingsClass;
$newRow->key = $field->attribute;
$newRow->value = $tempResource->{$field->attribute};
$newRow->save();
}
});
if (config('nova-settings.reload_page_on_save', false) === true) {
return response()->json(['reload' => true]);
}
return response('', 204);
}
public function deleteImage(Request $request, $pathName, $fieldName)
{
if (!NovaSettings::getAuthorizations('authorizedToUpdate')) return $this->unauthorized();
$existingRow = NovaSettings::getSettingsModel()::where('key', $fieldName)->first();
if (isset($existingRow)) {
$field = $this->findField(collect(NovaSettings::getFields($pathName)), $fieldName);
// Delete file if exists
if (isset($field) && $field instanceof \Laravel\Nova\Fields\File) {
$disk = $field->getStorageDisk();
Storage::disk($disk)->delete($existingRow->value);
}
$existingRow->value = null;
$existingRow->save();
}
return response('', 204);
}
/**
* Handle field preview requests for markdown and other previewable fields.
*
* @param \Illuminate\Http\Request $request
* @param string $path
* @param string $attribute
* @return \Illuminate\Http\JsonResponse
*/
public function fieldPreview(Request $request, $path, $attribute)
{
if (!NovaSettings::canSeeSettings()) return $this->unauthorized();
// Find the field in the settings
$field = $this->findField(collect(NovaSettings::getFields($path)), $attribute);
if (!$field) {
return response()->json(['error' => 'Field not found'], 404);
}
// Get the content to preview from the request
$content = $request->input('value', '');
return response()->json([
'preview' => $field->previewFor($content)
]);
}
protected function findField($fields, $fieldName)
{
if (empty($fields)) return null;
$field = $fields->firstWhere('attribute', $fieldName);
// Target field might be inside container field
if (empty($field)) {
foreach ($fields as $value) {
if ($value instanceof \Laravel\Nova\Panel) {
$field = $this->findField(collect($value->data), $fieldName);
if (!empty($field)) return $field;
}
if (class_exists('\Eminiarts\Tabs\Tabs') && $value instanceof \Eminiarts\Tabs\Tabs) {
$field = $this->findField(collect($value->data), $fieldName);
if (!empty($field)) return $field;
}
}
}
return $field;
}
protected function availableFields($path = 'general')
{
return (new FieldCollection($this->filter(NovaSettings::getFields($path))))->authorized(request());
}
protected function fields(Request $request, $path = 'general')
{
return NovaSettings::getFields($path);
}
protected function makeFakeResource(string $fieldName, $fieldValue)
{
$fakeResource = new \Laravel\Nova\Support\Fluent;
$fakeResource->{$fieldName} = $fieldValue;
return $fakeResource;
}
/**
* Return the panels for this request with the default label.
*
* @param string $label
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return array
*/
protected function panelsWithDefaultLabel($label, NovaRequest $request)
{
$method = $this->fieldsMethod($request);
return with(
collect(array_values($this->{$method}($request, $request->get('path', 'general'))))->whereInstanceOf(Panel::class)->unique('name')->values(),
function ($panels) use ($label) {
return $panels->when($panels->where('name', $label)->isEmpty(), function ($panels) use ($label) {
return $panels->prepend((new Panel($label))->withToolbar());
})->all();
}
);
}
protected function unauthorized()
{
return response()->json(['error' => 'Unauthorized'], 403);
}
protected function assignToPanels($label, FieldCollection $fields)
{
return $fields->map(function ($field) use ($label) {
if (!$field->panel) $field->panel = Panel::make($label);
return $field;
});
}
}
================================================
FILE: src/Http/Middleware/Authorize.php
================================================
<?php
namespace Outl1ne\NovaSettings\Http\Middleware;
use Laravel\Nova\Nova;
use Outl1ne\NovaSettings\NovaSettings;
class Authorize
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response
*/
public function handle($request, $next)
{
$tool = collect(Nova::registeredTools())->first([$this, 'matchesTool']);
return optional($tool)->authorize($request) ? $next($request) : abort(403);
}
/**
* Determine whether this tool belongs to the package.
*
* @param \Laravel\Nova\Tool $tool
* @return bool
*/
public function matchesTool($tool)
{
return $tool instanceof NovaSettings;
}
}
================================================
FILE: src/Http/Middleware/SettingsPathExists.php
================================================
<?php
namespace Outl1ne\NovaSettings\Http\Middleware;
use Outl1ne\NovaSettings\NovaSettings;
class SettingsPathExists
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response
*/
public function handle($request, $next)
{
$path = $request->get('path') ?: $request->route('path');
$path = !empty($path) ? trim($path) : 'general';
return NovaSettings::doesPathExist($path) ? $next($request) : abort(404);
}
}
================================================
FILE: src/Models/Settings.php
================================================
<?php
namespace Outl1ne\NovaSettings\Models;
use Outl1ne\NovaSettings\NovaSettings;
use Illuminate\Database\Eloquent\Model;
class Settings extends Model
{
protected $primaryKey = 'key';
public $incrementing = false;
public $timestamps = false;
public $fillable = ['key', 'value'];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->setTable(NovaSettings::getSettingsTableName());
}
protected static function booted()
{
static::updated(function ($setting) {
NovaSettings::getStore()->clearCache($setting->key);
});
}
public function setValueAttribute($value)
{
$this->casts = NovaSettings::getCasts();
$castType = null;
if ($this->hasCast($this->key)) $castType = $this->getCastType($this->key);
switch ($castType) {
case 'datetime':
case 'date':
$this->attributes['value'] = $value;
return;
default:
$this->attributes['value'] = is_array($value) || $value instanceof \JsonSerializable
? json_encode($value)
: $value;
}
}
public function getValueAttribute($value)
{
$originalCasts = $this->casts;
$this->casts = NovaSettings::getCasts();
if ($this->hasCast($this->key)) {
$value = $this->castAttribute($this->key, $value);
}
$this->casts = $originalCasts;
return $value;
}
public static function getValueForKey($key)
{
$setting = static::where('key', $key)->get()->first();
return isset($setting) ? $setting->value : null;
}
}
================================================
FILE: src/Nova/Resources/Settings.php
================================================
<?php
namespace Outl1ne\NovaSettings\Nova\Resources;
use Laravel\Nova\Resource;
use Illuminate\Http\Request;
use Outl1ne\NovaSettings\NovaSettings;
class Settings extends Resource
{
public static $title = 'key';
public static $model = null;
public static $displayInNavigation = false;
public function __construct($resource)
{
self::$model = NovaSettings::getSettingsModel();
parent::__construct($resource);
}
public function fields(Request $request)
{
return [];
}
}
================================================
FILE: src/NovaSettings.php
================================================
<?php
namespace Outl1ne\NovaSettings;
use Laravel\Nova\Nova;
use Laravel\Nova\Tool;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Laravel\Nova\Menu\MenuItem;
use Laravel\Nova\Menu\MenuSection;
use Outl1ne\NovaSettings\Models\Settings;
class NovaSettings extends Tool
{
public function boot()
{
Nova::script('nova-settings', __DIR__ . '/../dist/js/entry.js');
}
public function menu(Request $request)
{
$fields = static::getFields();
$basePath = config('nova-settings.base_path', 'nova-settings');
$isAuthorized = static::canSeeSettings();
$showInSidebar = config('nova-settings.show_in_sidebar', true);
if (!$isAuthorized || !$showInSidebar || empty($fields)) return null;
if (count($fields) == 1) {
return MenuSection::make(__('novaSettings.navigationItemTitle'))
->path($basePath . '/' . array_key_first($fields))
->icon('adjustments-vertical');
} else {
$menuItems = [];
foreach ($fields as $key => $fields) {
$menuItems[] = MenuItem::link(self::getPageName($key), "{$basePath}/{$key}");
}
return MenuSection::make(__('novaSettings.navigationItemTitle'), $menuItems)
->icon('adjustments-vertical')
->collapsable();
}
}
public static function getSettingsTableName(): string
{
return config('nova-settings.table', 'nova_settings');
}
public static function getPageName($key): string
{
if (__("novaSettings.$key") === "novaSettings.$key") {
return Str::title(str_replace('-', ' ', $key));
} else {
return __("novaSettings.$key");
}
}
public static function getAuthorizations($key = null)
{
$request = request();
$fakeResource = new \Outl1ne\NovaSettings\Nova\Resources\Settings(NovaSettings::getSettingsModel()::make());
$authorizations = [
'authorizedToView' => $fakeResource->authorizedToView($request),
'authorizedToCreate' => $fakeResource->authorizedToCreate($request),
'authorizedToUpdate' => $fakeResource->authorizedToUpdate($request),
'authorizedToDelete' => $fakeResource->authorizedToDelete($request),
];
return $key ? $authorizations[$key] : $authorizations;
}
public static function canSeeSettings()
{
$auths = static::getAuthorizations();
return $auths['authorizedToView'] || $auths['authorizedToUpdate'];
}
/**
* Define settings fields and an optional casts.
*
* @param array|callable $fields Array of fields/panels to be displayed or callable that returns an array.
* @param array $casts Associative array same as Laravel's $casts on models.
**/
public static function addSettingsFields($fields = [], $casts = [], $path = 'general')
{
return static::getStore()->addSettingsFields($fields, $casts, $path);
}
/**
* Define casts.
*
* @param array $casts Casts same as Laravel's casts on a model.
**/
public static function addCasts($casts = [])
{
return static::getStore()->addCasts($casts);
}
public static function getFields($path = null)
{
if (!$path) return static::getStore()->getRawFields();
return static::getStore()->getFields($path);
}
public static function clearFields()
{
return static::getStore()->clearFields();
}
public static function getCasts()
{
return static::getStore()->getCasts();
}
public static function getSetting($settingKey, $default = null)
{
return static::getStore()->getSetting($settingKey, $default);
}
public static function getSettings(?array $settingKeys = null, array $defaults = [])
{
return static::getStore()->getSettings($settingKeys, $defaults);
}
public static function setSettingValue($settingKey, $value = null)
{
return static::getStore()->setSettingValue($settingKey, $value);
}
public static function getSettingsModel(): string
{
return config('nova-settings.models.settings', Settings::class);
}
public static function doesPathExist($path)
{
return array_key_exists($path, static::getFields());
}
public static function getStore(): NovaSettingsStore
{
return app()->make(NovaSettingsStore::class);
}
}
================================================
FILE: src/NovaSettingsCacheStore.php
================================================
<?php
namespace Outl1ne\NovaSettings;
use Illuminate\Support\Facades\Cache;
use function collect;
use function is_array;
use function is_string;
class NovaSettingsCacheStore extends NovaSettingsStore
{
/** @var \Illuminate\Contracts\Cache\Repository */
private $cache;
public function __construct()
{
$this->cache = Cache::store(config('nova-settings.cache.store'));
}
public function clearCache($keyNames = null)
{
// Clear whole cache
if (empty($keyNames)) {
$this->getSettingsModelClass()::all(['key'])->each(function ($setting) {
$this->cache->forget($this->getCacheKey($setting->key));
});
return;
}
// Clear specific keys
if (is_string($keyNames)) $keyNames = [$keyNames];
foreach ($keyNames as $key) {
$this->cache->forget($this->getCacheKey($key));
}
}
protected function getCached($keyNames = null)
{
if (is_string($keyNames)) {
return $this->cache->get($this->getCacheKey($keyNames));
}
if (is_array($keyNames)) {
return collect($keyNames)
->mapWithKeys(function ($key) {
if (!$this->cache->has($this->getCacheKey($key))) return [];
return [$key => $this->getCached($key)];
})
->toArray();
}
return [];
}
protected function setCached($keyName, $value)
{
$this->cache->forever($this->getCacheKey($keyName), $value);
}
private function getCacheKey($key)
{
return config('nova-settings.cache.prefix', 'nova-settings:') . $key;
}
}
================================================
FILE: src/NovaSettingsInMemoryStore.php
================================================
<?php
namespace Outl1ne\NovaSettings;
use function collect;
use function is_array;
use function is_string;
class NovaSettingsInMemoryStore extends NovaSettingsStore
{
protected $cache = [];
public function clearCache($keyNames = null)
{
// Clear whole cache
if (empty($keyNames)) {
$this->cache = [];
return;
}
// Clear specific keys
if (is_string($keyNames)) $keyNames = [$keyNames];
foreach ($keyNames as $key) {
unset($this->cache[$key]);
}
}
protected function getCached($keyNames = null)
{
if (is_string($keyNames)) return $this->cache[$keyNames] ?? null;
return is_array($keyNames) && !empty($keyNames)
? collect($this->cache)->only($keyNames)->toArray()
: $this->cache;
}
protected function setCached($keyName, $value)
{
$this->cache[$keyName] = $value;
}
}
================================================
FILE: src/NovaSettingsNoCacheStore.php
================================================
<?php
namespace Outl1ne\NovaSettings;
use function is_string;
class NovaSettingsNoCacheStore extends NovaSettingsStore
{
public function clearCache($keyNames = null)
{
}
protected function getCached($keyNames = null)
{
if (is_string($keyNames)) return null;
return [];
}
protected function setCached($keyName, $value)
{
}
}
================================================
FILE: src/NovaSettingsServiceProvider.php
================================================
<?php
namespace Outl1ne\NovaSettings;
use Laravel\Nova\Nova;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Laravel\Nova\Http\Middleware\Authenticate;
use Outl1ne\NovaSettings\Http\Middleware\Authorize;
use Outl1ne\NovaTranslationsLoader\LoadsNovaTranslations;
use Outl1ne\NovaSettings\Http\Middleware\SettingsPathExists;
use function array_keys;
use function config;
use function config_path;
use function database_path;
use function in_array;
use function inertia;
use function is_array;
class NovaSettingsServiceProvider extends ServiceProvider
{
use LoadsNovaTranslations;
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
$this->loadTranslations(__DIR__ . '/../lang', 'nova-settings', true);
if ($this->app->runningInConsole()) {
// Publish migrations
$this->publishes([
__DIR__ . '/../database/migrations' => database_path('migrations'),
], 'migrations');
// Publish config
$this->publishes([
__DIR__ . '/../config/' => config_path(),
], 'config');
}
}
public function register()
{
$this->registerRoutes();
$this->mergeConfigFrom(
__DIR__ . '/../config/nova-settings.php',
'nova-settings'
);
$this->registerSettingsStore();
}
protected function registerSettingsStore()
{
$caching = config('nova-settings.cache.store');
if (is_array(config('cache.stores')) && in_array($caching, array_keys(config('cache.stores')))) {
$this->app->singleton(NovaSettingsStore::class, function () {
return new NovaSettingsCacheStore();
});
} else if ($caching === ':memory:') {
$this->app->singleton(NovaSettingsStore::class, function () {
return new NovaSettingsInMemoryStore();
});
} else {
$this->app->singleton(NovaSettingsStore::class, function () {
return new NovaSettingsNoCacheStore();
});
}
}
protected function registerRoutes()
{
// Register nova routes
Nova::router()->group(function ($router) {
$path = config('nova-settings.base_path', 'nova-settings');
$router
->get("{$path}/{pageId?}", fn ($pageId = 'general') => inertia('NovaSettings', ['basePath' => $path, 'pageId' => $pageId]))
->middleware(['nova', Authenticate::class])
->domain(config('nova.domain', null));
});
if ($this->app->routesAreCached()) return;
Route::middleware(['nova', Authorize::class, SettingsPathExists::class])
->domain(config('nova.domain', null))
->group(__DIR__ . '/../routes/api.php');
}
}
================================================
FILE: src/NovaSettingsStore.php
================================================
<?php
namespace Outl1ne\NovaSettings;
use Illuminate\Support\Str;
use function array_diff;
use function array_keys;
use function array_map;
use function array_merge;
use function call_user_func;
use function collect;
use function is_array;
use function is_callable;
abstract class NovaSettingsStore
{
protected $fields = [];
protected $casts = [];
public function addSettingsFields($fields = [], $casts = [], $path = 'general')
{
$path = Str::lower(Str::slug($path));
if (is_callable($fields)) $fields = [$fields];
$this->fields[$path] = array_merge($this->fields[$path] ?? [], $fields ?? []);
$this->casts = array_merge($this->casts, $casts ?? []);
return $this;
}
public function addCasts($casts = [])
{
$this->casts = array_merge($this->casts, $casts);
return $this;
}
public function getRawFields()
{
return $this->fields;
}
public function getFields($path = 'general')
{
$rawFields = array_map(function ($fieldItem) {
return is_callable($fieldItem) ? call_user_func($fieldItem) : $fieldItem;
}, $this->fields[$path] ?? $this->fields);
$fields = [];
foreach ($rawFields as $rawField) {
if (is_array($rawField)) $fields = array_merge($fields, $rawField);
else $fields[] = $rawField;
}
return $fields;
}
public function getCasts()
{
return $this->casts;
}
public function getSetting($settingKey, $default = null)
{
if ($cached = $this->getCached($settingKey)) return $cached;
$settingValue = $this->getSettingsModelClass()::getValueForKey($settingKey) ?? $default;
$this->setCached($settingKey, $settingValue);
return $settingValue;
}
public function getSettings(?array $settingKeys = null, array $defaults = [])
{
if (!empty($settingKeys)) {
$cached = $this->getCached($settingKeys);
$hasMissingKeys = !empty(array_diff($settingKeys, array_keys($cached)));
if (!$hasMissingKeys) return $cached;
$settings = $this->getSettingsModelClass()::whereIn('key', $settingKeys)
->get()
->pluck('value', 'key');
return collect($settingKeys)->flatMap(function ($settingKey) use ($settings, $defaults) {
$settingValue = $settings[$settingKey] ?? null;
if (isset($settingValue)) {
$this->setCached($settingKey, $settingValue);
return [$settingKey => $settingValue];
} else {
$defaultValue = $defaults[$settingKey] ?? null;
return [$settingKey => $defaultValue];
}
})->toArray();
}
return $this->getSettingsModelClass()::all()
->tap(function ($settings) {
$settings->each(function ($setting) {
$this->setCached($setting->key, $setting->value);
});
})
->pluck('value', 'key')
->toArray();
}
public function setSettingValue($settingKey, $value = null)
{
$setting = $this->getSettingsModelClass()::firstOrCreate(['key' => $settingKey]);
$setting->value = $value;
$setting->save();
$this->setCached($settingKey, $setting->value);
return $setting;
}
public abstract function clearCache($keyNames = null);
public function clearFields()
{
$this->fields = [];
$this->casts = [];
$this->clearCache();
}
protected abstract function getCached($keyNames = null);
protected abstract function setCached($keyName, $value);
/**
* @return class-string<\Outl1ne\NovaSettings\Models\Settings>
*/
protected function getSettingsModelClass()
{
return NovaSettings::getSettingsModel();
}
}
================================================
FILE: src/helpers.php
================================================
<?php
use Outl1ne\NovaSettings\NovaSettings;
if (!function_exists('nova_get_settings')) {
function nova_get_settings($settingKeys = null, $defaults = [])
{
return NovaSettings::getSettings($settingKeys, $defaults);
}
}
if (!function_exists('nova_get_setting')) {
function nova_get_setting($settingKey, $default = null)
{
return NovaSettings::getSetting($settingKey, $default);
}
}
if (!function_exists('nova_set_setting_value')) {
function nova_set_setting_value($settingKey, $value = null)
{
return NovaSettings::setSettingValue($settingKey, $value);
}
}
================================================
FILE: testbench.yaml
================================================
laravel: ./vendor/laravel/nova-dusk-suite
providers:
- Laravel\Nova\NovaServiceProvider
================================================
FILE: tests/Browser/DetailTest.php
================================================
<?php
namespace Outl1ne\NovaSettings\Tests\Browser;
use App\Models\User;
use Laravel\Dusk\Browser;
use Outl1ne\NovaSettings\Tests\DuskTestCase;
class DetailTest extends DuskTestCase
{
public function test_settings_appears_in_sidebar_with_no_fields()
{
$this->setupLaravel();
$this->browse(function (Browser $browser) {
$browser->loginAs(User::find(1))
->visit('nova');
dump($browser->element('*')->getAttribute('innerHTML'));
$browser
->assertSee('Settings');
$browser->blank();
});
}
public function test_settings_appears_in_sidebar_with_fields()
{
$this->setupLaravel();
$this->browse(function (Browser $browser) {
$browser->loginAs(User::find(1))
->visit('nova')
->assertSee('Settings');
$browser->blank();
});
}
public function test_can_navigate_into_and_render_settings()
{
$this->browse(function (Browser $browser) {
$browser->loginAs(User::find(1))
->visit('nova')
->assertVisible('@nova-settings')
->pause(1500)
->click('@nova-settings')
->waitFor('@nova-settings-form')
->assertSee('Hello Field');
$browser->blank();
});
}
}
================================================
FILE: tests/DuskTestCase.php
================================================
<?php
namespace Outl1ne\NovaSettings\Tests;
use Laravel\Nova\Nova;
use Laravel\Dusk\Browser;
use Laravel\Nova\Fields\Text;
use Outl1ne\NovaSettings\NovaSettings;
use Illuminate\Foundation\Application;
abstract class DuskTestCase extends \Orchestra\Testbench\Dusk\TestCase
{
/**
* The base serve host URL to use while testing the application.
*
* @var string
*/
protected static $baseServeHost = '127.0.0.1';
/**
* The base serve port to use while testing the application.
*
* @var int
*/
protected static $baseServePort = 8085;
/**
* Server specific setup. It may share alot with the main setUp() method, but
* should exclude things like DB migrations so we don't end up wiping the
* DB content mid test. Using this method means we can be explicit.
*
* @return void
*/
protected function setUpDuskServer(): void
{
parent::setUp();
Nova::tools([
new NovaSettings(),
]);
NovaSettings::addSettingsFields([
Text::make('Hello Field', 'hello_field'),
]);
tap($this->app->make('config'), function ($config) {
$config->set('app.url', static::baseServeUrl());
$config->set('filesystems.disks.public.url', static::baseServeUrl() . '/storage');
});
}
/**
* Get base path.
*
* @return string
*/
protected function getBasePath()
{
return realpath(__DIR__ . '/../vendor/laravel/nova-dusk-suite');
}
/**
* Get package providers.
*
* @param \Illuminate\Foundation\Application $app
*
* @return array
*/
protected function getPackageProviders($app)
{
return [
'Fideloper\Proxy\TrustedProxyServiceProvider',
'Laravel\Nova\NovaCoreServiceProvider',
'Carbon\Laravel\ServiceProvider',
'Outl1ne\NovaSettings\NovaSettingsServiceProvider',
];
}
/**
* Get application aliases.
*
* @param \Illuminate\Foundation\Application $app
*
* @return array
*/
protected function getApplicationAliases($app)
{
return $app['config']['app.aliases'];
}
/**
* Get application providers.
*
* @param \Illuminate\Foundation\Application $app
*
* @return array
*/
protected function getApplicationProviders($app)
{
return $app['config']['app.providers'];
}
/**
* Resolve application implementation.
*
* @return \Illuminate\Foundation\Application
*/
protected function resolveApplication()
{
return tap(new Application($this->getBasePath()), function ($app) {
$app->detectEnvironment(function () {
return 'testing';
});
});
}
/**
* Resolve application Console Kernel implementation.
*
* @param \Illuminate\Foundation\Application $app
*
* @return void
*/
protected function resolveApplicationConsoleKernel($app)
{
$app->singleton('Illuminate\Contracts\Console\Kernel', 'App\Console\Kernel');
}
/**
* Resolve application HTTP Kernel implementation.
*
* @param \Illuminate\Foundation\Application $app
*
* @return void
*/
protected function resolveApplicationHttpKernel($app)
{
$app->singleton('Illuminate\Contracts\Http\Kernel', 'App\Http\Kernel');
}
/**
* Resolve application HTTP exception handler.
*
* @param \Illuminate\Foundation\Application $app
*
* @return void
*/
protected function resolveApplicationExceptionHandler($app)
{
$app->singleton('Illuminate\Contracts\Debug\ExceptionHandler', 'App\Exceptions\Handler');
}
/**
* Setup Laravel for the test.
*
* @param callable|null $callback
* @return void
*/
protected function setupLaravel(callable $callback = null)
{
$this->artisan('migrate:fresh')->run();
$this->artisan('db:seed', ['--class' => \Database\Seeders\DatabaseSeeder::class])->run();
if (is_callable($callback)) {
$callback($this->app);
}
}
/**
* Run the given callback with searchable functionality enabled.
*
* @param callable $callback
* @return void
*/
protected function whileSearchable(callable $callback)
{
touch(base_path('.searchable'));
try {
$callback();
} finally {
@unlink(base_path('.searchable'));
}
}
/**
* Run the given callback with inline-create functionality enabled.
*
* @param callable $callback
* @return void
*/
protected function whileInlineCreate(callable $callback)
{
touch(base_path('.inline-create'));
try {
$callback();
} finally {
@unlink(base_path('.inline-create'));
}
}
/**
* Create a new Browser instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @return \Laravel\Dusk\Browser
*/
protected function newBrowser($driver)
{
return tap(new Browser($driver), function ($browser) {
$browser->resize(env('DUSK_WIDTH'), env('DUSK_HEIGHT'));
});
}
protected function captureFailuresFor($browsers)
{
$browsers->each(function (Browser $browser, $key) {
$name = str_replace('\\', '_', get_class($this)) . '_' . $this->getName(false);
$browser->screenshot('failure-' . $this->getName() . '-' . $key);
});
}
}
================================================
FILE: tests/Feature/NavigationTest.php
================================================
<?php
namespace Outl1ne\NovaSettings\Tests\Feature;
use Laravel\Nova\Fields\Text;
use Outl1ne\NovaSettings\NovaSettings;
use Outl1ne\NovaSettings\Tests\IntegrationTestCase;
class NavigationTest extends IntegrationTestCase
{
public function test_general_navigation_renders_with_no_fields()
{
$settingsTool = new NovaSettings;
$navigationView = $settingsTool->renderNavigation()->render();
$this->assertStringContainsString('dusk="nova-settings"', $navigationView);
}
public function test_general_navigation_renders_with_fields()
{
NovaSettings::addSettingsFields([
Text::make('Test'),
]);
$settingsTool = new NovaSettings;
$navigationView = $settingsTool->renderNavigation()->render();
$this->assertStringContainsString('dusk="nova-settings"', $navigationView);
}
public function test_multiple_navigation_renders()
{
NovaSettings::addSettingsFields([
Text::make('Test'),
]);
NovaSettings::addSettingsFields([
Text::make('TestTwo'),
], [], 'Other');
$settingsTool = new NovaSettings;
$navigationView = $settingsTool->renderNavigation()->render();
$this->assertStringContainsString('dusk="nova-settings-general"', $navigationView);
$this->assertStringContainsString('dusk="nova-settings-other"', $navigationView);
}
}
================================================
FILE: tests/Feature/SettingsCastTest.php
================================================
<?php
namespace Outl1ne\NovaSettings\Tests\Feature;
use Laravel\Nova\Fields\Number;
use Outl1ne\NovaSettings\NovaSettings;
use Outl1ne\NovaSettings\Models\Settings;
use Outl1ne\NovaSettings\Tests\IntegrationTestCase;
class SettingsCastTest extends IntegrationTestCase
{
public function test_integer_casting_works()
{
NovaSettings::addSettingsFields([
Number::make('Test'),
], ['test' => 'int']);
Settings::create(['key' => 'test', 'value' => '555']);
$settingValue = nova_get_setting('test');
$this->assertIsInt($settingValue);
$this->assertEquals(555, $settingValue);
}
public function test_array_casting_works()
{
NovaSettings::addSettingsFields([
Number::make('Test'),
], ['test' => 'array']);
$testValue = ['et' => 'Eesti', 'ru' => 'Russia'];
Settings::create(['key' => 'test', 'value' => $testValue]);
$settingValue = nova_get_setting('test');
$this->assertIsArray($settingValue);
$this->assertEquals($testValue, $settingValue);
}
public function test_boolean_casting_works()
{
NovaSettings::addSettingsFields([
Number::make('Test'),
], ['test' => 'boolean']);
Settings::create(['key' => 'test', 'value' => 1]);
$settingValue = nova_get_setting('test');
$this->assertIsBool($settingValue);
$this->assertTrue($settingValue);
}
}
================================================
FILE: tests/Feature/SettingsHelpersTest.php
================================================
<?php
namespace Outl1ne\NovaSettings\Tests\Feature;
use Outl1ne\NovaSettings\Models\Settings;
use Outl1ne\NovaSettings\Tests\IntegrationTestCase;
class SettingsHelpersTest extends IntegrationTestCase
{
public function test_nova_get_setting_works()
{
Settings::create(['key' => 'test', 'value' => '555']);
$this->assertEquals('555', nova_get_setting('test'));
}
public function test_nova_get_settings_works()
{
Settings::create(['key' => 'test', 'value' => '555']);
Settings::create(['key' => 'testtwo', 'value' => '123']);
$this->assertEquals(['test' => '555', 'testtwo' => '123'], nova_get_settings(['test', 'testtwo']));
}
}
================================================
FILE: tests/Feature/SettingsRetrieveTest.php
================================================
<?php
namespace Outl1ne\NovaSettings\Tests\Feature;
use Laravel\Nova\Fields\Text;
use Outl1ne\NovaSettings\NovaSettings;
use Outl1ne\NovaSettings\Tests\IntegrationTestCase;
class SettingsRetrieveTest extends IntegrationTestCase
{
public function test_general_fields_are_returned_with_no_path()
{
NovaSettings::addSettingsFields([
Text::make('Test'),
Text::make('TestOne'),
]);
NovaSettings::addSettingsFields([
Text::make('TestTwo'),
Text::make('TestThree'),
Text::make('TestFour'),
], [], 'Other');
$request = $this->getJson(route('nova-settings.get'));
$request->assertStatus(200);
$request->assertJsonCount(2, 'fields');
}
public function test_general_fields_are_returned_with_general_path()
{
NovaSettings::addSettingsFields([
Text::make('Test'),
Text::make('TestOne'),
]);
NovaSettings::addSettingsFields([
Text::make('TestTwo'),
Text::make('TestThree'),
Text::make('TestFour'),
], [], 'Other');
$request = $this->getJson(route('nova-settings.get', ['path' => 'general']));
$request->assertStatus(200);
$request->assertJsonCount(2, 'fields');
}
public function test_other_fields_are_returned_with_other_path()
{
NovaSettings::addSettingsFields([
Text::make('Test'),
Text::make('TestOne'),
]);
NovaSettings::addSettingsFields([
Text::make('TestTwo'),
Text::make('TestThree'),
Text::make('TestFour'),
], [], 'Other');
$request = $this->getJson(route('nova-settings.get', ['path' => 'other']));
$request->assertStatus(200);
$request->assertJsonCount(3, 'fields');
}
}
================================================
FILE: tests/Feature/SettingsSaveTest.php
================================================
<?php
namespace Outl1ne\NovaSettings\Tests\Feature;
use Laravel\Nova\Fields\Text;
use Outl1ne\NovaSettings\NovaSettings;
use Outl1ne\NovaSettings\Models\Settings;
use Outl1ne\NovaSettings\Tests\IntegrationTestCase;
class SettingsSaveTest extends IntegrationTestCase
{
public function test_settings_are_saved()
{
NovaSettings::addSettingsFields([
Text::make('Test'),
Text::make('TestOne'),
]);
$request = $this->postJson(route('nova-settings.save'), ['test' => 'Test Value']);
$request->assertStatus(204);
$this->assertEquals('Test Value', Settings::getValueForKey('test'));
}
public function test_settings_are_saved_with_path()
{
NovaSettings::addSettingsFields([
Text::make('TestTwo'),
Text::make('TestThree'),
Text::make('TestFour'),
], [], 'Other');
$request = $this->postJson(route('nova-settings.save'), ['path' => 'other', 'testthree' => 'Test Value']);
$request->assertStatus(204);
$this->assertEquals('Test Value', Settings::getValueForKey('testthree'));
}
}
================================================
FILE: tests/IntegrationTestCase.php
================================================
<?php
namespace Outl1ne\NovaSettings\Tests;
use Laravel\Nova\Nova;
use Laravel\Nova\NovaServiceProvider;
use Illuminate\Support\Facades\Route;
use Outl1ne\NovaSettings\NovaSettings;
use Orchestra\Testbench\TestCase as Orchestra;
use Outl1ne\NovaSettings\NovaSettingsServiceProvider;
abstract class IntegrationTestCase extends Orchestra
{
public function setUp(): void
{
parent::setUp();
NovaSettings::clearFields();
Route::middlewareGroup('nova', []);
Nova::$tools = [
new NovaSettings,
];
$this->setUpDatabase($this->app);
}
protected function getPackageProviders($app)
{
return [
NovaServiceProvider::class,
NovaSettingsServiceProvider::class,
];
}
protected function setUpDatabase()
{
$this->artisan('migrate:fresh');
}
}
================================================
FILE: tests/bootstrap.php
================================================
<?php
use Dotenv\Dotenv;
use Illuminate\Support\Env;
require __DIR__.'/../vendor/autoload.php';
Dotenv::create(
Env::getRepository(),
__DIR__.'/../',
'.env.dusk'
)->safeLoad();
if (isset($_SERVER['CI']) || isset($_ENV['CI'])) {
Orchestra\Testbench\Dusk\Options::withoutUI();
} else {
Orchestra\Testbench\Dusk\Options::withUI();
}
================================================
FILE: webpack.mix.js
================================================
let mix = require('laravel-mix');
let path = require('path');
mix.extend('nova', new require('laravel-nova-devtool'));
mix
.setPublicPath('dist')
.js('resources/js/entry.js', 'js')
.vue({ version: 3 })
.nova('outl1ne/nova-settings')
.alias({
'laravel-nova': path.join(__dirname, 'vendor/laravel/nova/resources/js/mixins/packages.js'),
});
gitextract_ete0td8v/ ├── .editorconfig ├── .gitattributes ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config/ │ └── nova-settings.php ├── database/ │ └── migrations/ │ ├── 2019_08_13_000000_create_nova_settings_table.php │ └── 2021_02_15_000000_update_nova_settings_value_column.php ├── dist/ │ ├── js/ │ │ ├── entry.js │ │ └── entry.js.LICENSE.txt │ └── mix-manifest.json ├── lang/ │ ├── ar.json │ ├── de.json │ ├── en.json │ ├── es.json │ ├── et.json │ ├── fa.json │ ├── fr.json │ ├── it.json │ ├── nl.json │ ├── pt-BR.json │ ├── ru.json │ ├── sk.json │ ├── tr.json │ └── uz.json ├── package.json ├── phpunit.dusk.xml.dist ├── phpunit.xml.dist ├── resources/ │ └── js/ │ ├── entry.js │ └── views/ │ └── Settings.vue ├── routes/ │ └── api.php ├── src/ │ ├── Http/ │ │ ├── Controllers/ │ │ │ └── SettingsController.php │ │ └── Middleware/ │ │ ├── Authorize.php │ │ └── SettingsPathExists.php │ ├── Models/ │ │ └── Settings.php │ ├── Nova/ │ │ └── Resources/ │ │ └── Settings.php │ ├── NovaSettings.php │ ├── NovaSettingsCacheStore.php │ ├── NovaSettingsInMemoryStore.php │ ├── NovaSettingsNoCacheStore.php │ ├── NovaSettingsServiceProvider.php │ ├── NovaSettingsStore.php │ └── helpers.php ├── testbench.yaml ├── tests/ │ ├── Browser/ │ │ └── DetailTest.php │ ├── DuskTestCase.php │ ├── Feature/ │ │ ├── NavigationTest.php │ │ ├── SettingsCastTest.php │ │ ├── SettingsHelpersTest.php │ │ ├── SettingsRetrieveTest.php │ │ └── SettingsSaveTest.php │ ├── IntegrationTestCase.php │ └── bootstrap.php └── webpack.mix.js
SYMBOL INDEX (130 symbols across 23 files)
FILE: database/migrations/2019_08_13_000000_create_nova_settings_table.php
method up (line 15) | public function up()
method down (line 29) | public function down()
FILE: database/migrations/2021_02_15_000000_update_nova_settings_value_column.php
method up (line 15) | public function up()
method down (line 30) | public function down()
FILE: dist/js/entry.js
function l (line 2) | function l(t){return l="function"==typeof Symbol&&"symbol"==typeof Symbo...
function f (line 2) | function f(){f=function(){return e};var t,e={},r=Object.prototype,n=r.ha...
function p (line 2) | function p(t,e,r,n,o,a,i){try{var c=t[a](i),s=c.value}catch(t){return vo...
function d (line 2) | function d(t){return function(){var e=this,r=arguments;return new Promis...
function r (line 2) | function r(n){var o=e[n];if(void 0!==o)return o.exports;var a=e[n]={expo...
FILE: src/Http/Controllers/SettingsController.php
class SettingsController (line 17) | class SettingsController extends Controller
method get (line 21) | public function get(Request $request)
method save (line 57) | public function save(NovaRequest $request)
method deleteImage (line 112) | public function deleteImage(Request $request, $pathName, $fieldName)
method fieldPreview (line 141) | public function fieldPreview(Request $request, $path, $attribute)
method findField (line 160) | protected function findField($fields, $fieldName)
method availableFields (line 184) | protected function availableFields($path = 'general')
method fields (line 189) | protected function fields(Request $request, $path = 'general')
method makeFakeResource (line 194) | protected function makeFakeResource(string $fieldName, $fieldValue)
method panelsWithDefaultLabel (line 208) | protected function panelsWithDefaultLabel($label, NovaRequest $request)
method unauthorized (line 222) | protected function unauthorized()
method assignToPanels (line 227) | protected function assignToPanels($label, FieldCollection $fields)
FILE: src/Http/Middleware/Authorize.php
class Authorize (line 8) | class Authorize
method handle (line 17) | public function handle($request, $next)
method matchesTool (line 30) | public function matchesTool($tool)
FILE: src/Http/Middleware/SettingsPathExists.php
class SettingsPathExists (line 8) | class SettingsPathExists
method handle (line 17) | public function handle($request, $next)
FILE: src/Models/Settings.php
class Settings (line 8) | class Settings extends Model
method __construct (line 15) | public function __construct(array $attributes = [])
method booted (line 21) | protected static function booted()
method setValueAttribute (line 28) | public function setValueAttribute($value)
method getValueAttribute (line 48) | public function getValueAttribute($value)
method getValueForKey (line 62) | public static function getValueForKey($key)
FILE: src/Nova/Resources/Settings.php
class Settings (line 9) | class Settings extends Resource
method __construct (line 15) | public function __construct($resource)
method fields (line 21) | public function fields(Request $request)
FILE: src/NovaSettings.php
class NovaSettings (line 13) | class NovaSettings extends Tool
method boot (line 15) | public function boot()
method menu (line 20) | public function menu(Request $request)
method getSettingsTableName (line 45) | public static function getSettingsTableName(): string
method getPageName (line 50) | public static function getPageName($key): string
method getAuthorizations (line 59) | public static function getAuthorizations($key = null)
method canSeeSettings (line 74) | public static function canSeeSettings()
method addSettingsFields (line 86) | public static function addSettingsFields($fields = [], $casts = [], $p...
method addCasts (line 96) | public static function addCasts($casts = [])
method getFields (line 101) | public static function getFields($path = null)
method clearFields (line 107) | public static function clearFields()
method getCasts (line 112) | public static function getCasts()
method getSetting (line 117) | public static function getSetting($settingKey, $default = null)
method getSettings (line 122) | public static function getSettings(?array $settingKeys = null, array $...
method setSettingValue (line 127) | public static function setSettingValue($settingKey, $value = null)
method getSettingsModel (line 132) | public static function getSettingsModel(): string
method doesPathExist (line 137) | public static function doesPathExist($path)
method getStore (line 142) | public static function getStore(): NovaSettingsStore
FILE: src/NovaSettingsCacheStore.php
class NovaSettingsCacheStore (line 11) | class NovaSettingsCacheStore extends NovaSettingsStore
method __construct (line 16) | public function __construct()
method clearCache (line 21) | public function clearCache($keyNames = null)
method getCached (line 39) | protected function getCached($keyNames = null)
method setCached (line 58) | protected function setCached($keyName, $value)
method getCacheKey (line 63) | private function getCacheKey($key)
FILE: src/NovaSettingsInMemoryStore.php
class NovaSettingsInMemoryStore (line 9) | class NovaSettingsInMemoryStore extends NovaSettingsStore
method clearCache (line 13) | public function clearCache($keyNames = null)
method getCached (line 28) | protected function getCached($keyNames = null)
method setCached (line 37) | protected function setCached($keyName, $value)
FILE: src/NovaSettingsNoCacheStore.php
class NovaSettingsNoCacheStore (line 7) | class NovaSettingsNoCacheStore extends NovaSettingsStore
method clearCache (line 9) | public function clearCache($keyNames = null)
method getCached (line 13) | protected function getCached($keyNames = null)
method setCached (line 20) | protected function setCached($keyName, $value)
FILE: src/NovaSettingsServiceProvider.php
class NovaSettingsServiceProvider (line 21) | class NovaSettingsServiceProvider extends ServiceProvider
method boot (line 30) | public function boot()
method register (line 48) | public function register()
method registerSettingsStore (line 60) | protected function registerSettingsStore()
method registerRoutes (line 79) | protected function registerRoutes()
FILE: src/NovaSettingsStore.php
class NovaSettingsStore (line 16) | abstract class NovaSettingsStore
method addSettingsFields (line 21) | public function addSettingsFields($fields = [], $casts = [], $path = '...
method addCasts (line 33) | public function addCasts($casts = [])
method getRawFields (line 39) | public function getRawFields()
method getFields (line 44) | public function getFields($path = 'general')
method getCasts (line 59) | public function getCasts()
method getSetting (line 64) | public function getSetting($settingKey, $default = null)
method getSettings (line 75) | public function getSettings(?array $settingKeys = null, array $default...
method setSettingValue (line 111) | public function setSettingValue($settingKey, $value = null)
method clearCache (line 122) | public abstract function clearCache($keyNames = null);
method clearFields (line 124) | public function clearFields()
method getCached (line 132) | protected abstract function getCached($keyNames = null);
method setCached (line 134) | protected abstract function setCached($keyName, $value);
method getSettingsModelClass (line 139) | protected function getSettingsModelClass()
FILE: src/helpers.php
function nova_get_settings (line 6) | function nova_get_settings($settingKeys = null, $defaults = [])
function nova_get_setting (line 13) | function nova_get_setting($settingKey, $default = null)
function nova_set_setting_value (line 20) | function nova_set_setting_value($settingKey, $value = null)
FILE: tests/Browser/DetailTest.php
class DetailTest (line 9) | class DetailTest extends DuskTestCase
method test_settings_appears_in_sidebar_with_no_fields (line 11) | public function test_settings_appears_in_sidebar_with_no_fields()
method test_settings_appears_in_sidebar_with_fields (line 28) | public function test_settings_appears_in_sidebar_with_fields()
method test_can_navigate_into_and_render_settings (line 41) | public function test_can_navigate_into_and_render_settings()
FILE: tests/DuskTestCase.php
class DuskTestCase (line 11) | abstract class DuskTestCase extends \Orchestra\Testbench\Dusk\TestCase
method setUpDuskServer (line 34) | protected function setUpDuskServer(): void
method getBasePath (line 57) | protected function getBasePath()
method getPackageProviders (line 69) | protected function getPackageProviders($app)
method getApplicationAliases (line 86) | protected function getApplicationAliases($app)
method getApplicationProviders (line 98) | protected function getApplicationProviders($app)
method resolveApplication (line 108) | protected function resolveApplication()
method resolveApplicationConsoleKernel (line 124) | protected function resolveApplicationConsoleKernel($app)
method resolveApplicationHttpKernel (line 136) | protected function resolveApplicationHttpKernel($app)
method resolveApplicationExceptionHandler (line 148) | protected function resolveApplicationExceptionHandler($app)
method setupLaravel (line 159) | protected function setupLaravel(callable $callback = null)
method whileSearchable (line 175) | protected function whileSearchable(callable $callback)
method whileInlineCreate (line 192) | protected function whileInlineCreate(callable $callback)
method newBrowser (line 209) | protected function newBrowser($driver)
method captureFailuresFor (line 216) | protected function captureFailuresFor($browsers)
FILE: tests/Feature/NavigationTest.php
class NavigationTest (line 9) | class NavigationTest extends IntegrationTestCase
method test_general_navigation_renders_with_no_fields (line 11) | public function test_general_navigation_renders_with_no_fields()
method test_general_navigation_renders_with_fields (line 18) | public function test_general_navigation_renders_with_fields()
method test_multiple_navigation_renders (line 30) | public function test_multiple_navigation_renders()
FILE: tests/Feature/SettingsCastTest.php
class SettingsCastTest (line 10) | class SettingsCastTest extends IntegrationTestCase
method test_integer_casting_works (line 12) | public function test_integer_casting_works()
method test_array_casting_works (line 25) | public function test_array_casting_works()
method test_boolean_casting_works (line 39) | public function test_boolean_casting_works()
FILE: tests/Feature/SettingsHelpersTest.php
class SettingsHelpersTest (line 8) | class SettingsHelpersTest extends IntegrationTestCase
method test_nova_get_setting_works (line 10) | public function test_nova_get_setting_works()
method test_nova_get_settings_works (line 17) | public function test_nova_get_settings_works()
FILE: tests/Feature/SettingsRetrieveTest.php
class SettingsRetrieveTest (line 9) | class SettingsRetrieveTest extends IntegrationTestCase
method test_general_fields_are_returned_with_no_path (line 11) | public function test_general_fields_are_returned_with_no_path()
method test_general_fields_are_returned_with_general_path (line 30) | public function test_general_fields_are_returned_with_general_path()
method test_other_fields_are_returned_with_other_path (line 49) | public function test_other_fields_are_returned_with_other_path()
FILE: tests/Feature/SettingsSaveTest.php
class SettingsSaveTest (line 10) | class SettingsSaveTest extends IntegrationTestCase
method test_settings_are_saved (line 12) | public function test_settings_are_saved()
method test_settings_are_saved_with_path (line 25) | public function test_settings_are_saved_with_path()
FILE: tests/IntegrationTestCase.php
class IntegrationTestCase (line 12) | abstract class IntegrationTestCase extends Orchestra
method setUp (line 14) | public function setUp(): void
method getPackageProviders (line 27) | protected function getPackageProviders($app)
method setUpDatabase (line 35) | protected function setUpDatabase()
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (91K chars).
[
{
"path": ".editorconfig",
"chars": 285,
"preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\ntrim_"
},
{
"path": ".gitattributes",
"chars": 463,
"preview": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and"
},
{
"path": ".github/FUNDING.yml",
"chars": 16,
"preview": "github: outl1ne\n"
},
{
"path": ".gitignore",
"chars": 179,
"preview": "/.idea\n/vendor\n/node_modules\ncomposer.phar\ncomposer.lock\nphpunit.xml\n.phpunit.result.cache\n.DS_Store\nThumbs.db\n.env.dusk"
},
{
"path": ".prettierrc",
"chars": 99,
"preview": "{\n \"printWidth\": 120,\n \"singleQuote\": true,\n \"trailingComma\": \"es5\",\n \"arrowParens\": \"avoid\"\n}\n"
},
{
"path": "CHANGELOG.md",
"chars": 4109,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
},
{
"path": "LICENSE.md",
"chars": 1091,
"preview": "MIT License\n\nCopyright (c) 2019 Outl1ne <info@optimistdigital.com>\n\nPermission is hereby granted, free of charge, to any"
},
{
"path": "README.md",
"chars": 6128,
"preview": "# Nova Settings\n\n[=>{var t={262:(t,e)=>{\"use strict\";e.A=(t,e)=>{const r"
},
{
"path": "dist/js/entry.js.LICENSE.txt",
"chars": 149,
"preview": "/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/rege"
},
{
"path": "dist/mix-manifest.json",
"chars": 39,
"preview": "{\n \"/js/entry.js\": \"/js/entry.js\"\n}\n"
},
{
"path": "lang/ar.json",
"chars": 280,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"الإعدادات\",\n \"novaSettings.saveButtonText\": \"حفظ الإعدادات\",\n \"novaSettings.n"
},
{
"path": "lang/de.json",
"chars": 285,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"Einstellungen\",\n \"novaSettings.saveButtonText\": \"Einstellungen speichern\",\n \""
},
{
"path": "lang/en.json",
"chars": 293,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"Settings\",\n \"novaSettings.saveButtonText\": \"Save settings\",\n \"novaSettings.no"
},
{
"path": "lang/es.json",
"chars": 337,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"Configuración\",\n \"novaSettings.saveButtonText\": \"Guardar configuración\",\n \"no"
},
{
"path": "lang/et.json",
"chars": 280,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"Seaded\",\n \"novaSettings.saveButtonText\": \"Salvesta seaded\",\n \"novaSettings.no"
},
{
"path": "lang/fa.json",
"chars": 266,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"تنظیمات\",\n \"novaSettings.saveButtonText\": \"ذخیره تنظیمات\",\n \"novaSettings.noS"
},
{
"path": "lang/fr.json",
"chars": 331,
"preview": "{\r\n \"novaSettings.navigationItemTitle\": \"Paramètres\",\r\n \"novaSettings.saveButtonText\": \"Enregistrer paramètres\",\r\n"
},
{
"path": "lang/it.json",
"chars": 315,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"Impostazioni\",\n \"novaSettings.saveButtonText\": \"Salva impostazioni\",\n \"novaSe"
},
{
"path": "lang/nl.json",
"chars": 292,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"Instellingen\",\n \"novaSettings.saveButtonText\": \"Instellingen opslaan\",\n \"nova"
},
{
"path": "lang/pt-BR.json",
"chars": 315,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"Configurações\",\n \"novaSettings.saveButtonText\": \"Salvar configurações\",\n \"nov"
},
{
"path": "lang/ru.json",
"chars": 279,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"Настройки\",\n \"novaSettings.saveButtonText\": \"Сохранить\",\n \"novaSettings.noSet"
},
{
"path": "lang/sk.json",
"chars": 313,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"Nastavenia\",\n \"novaSettings.saveButtonText\": \"Uložiť nastavenia\",\n \"novaSetti"
},
{
"path": "lang/tr.json",
"chars": 291,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"Ayarlar\",\n \"novaSettings.saveButtonText\": \"Ayarları Kaydet\",\n \"novaSettings.n"
},
{
"path": "lang/uz.json",
"chars": 291,
"preview": "{\n \"novaSettings.navigationItemTitle\": \"Sozlamalar\",\n \"novaSettings.saveButtonText\": \"Saqlash\",\n \"novaSettings.noSett"
},
{
"path": "package.json",
"chars": 690,
"preview": "{\n \"private\": true,\n \"scripts\": {\n \"dev\": \"mix\",\n \"watch\": \"mix watch\",\n \"hot\": \"mix watch --hot\",\n \"prod\""
},
{
"path": "phpunit.dusk.xml.dist",
"chars": 903,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSch"
},
{
"path": "phpunit.xml.dist",
"chars": 682,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" bootstrap=\"vendor/"
},
{
"path": "resources/js/entry.js",
"chars": 112,
"preview": "Nova.booting((Vue, router, store) => {\n Nova.inertia('NovaSettings', require('./views/Settings').default);\n});\n"
},
{
"path": "resources/js/views/Settings.vue",
"chars": 4387,
"preview": "<template>\n <LoadingView :loading=\"loading\" :key=\"pageId\">\n <Head :title=\"__('novaSettings.navigationItemTitle') + ("
},
{
"path": "routes/api.php",
"chars": 1025,
"preview": "<?php\n\nuse Illuminate\\Support\\Facades\\Route;\n\n/*\n|----------------------------------------------------------------------"
},
{
"path": "src/Http/Controllers/SettingsController.php",
"chars": 8561,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Http\\Controllers;\n\nuse Laravel\\Nova\\Panel;\nuse Illuminate\\Http\\Request;\nuse Larave"
},
{
"path": "src/Http/Middleware/Authorize.php",
"chars": 781,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Http\\Middleware;\n\nuse Laravel\\Nova\\Nova;\nuse Outl1ne\\NovaSettings\\NovaSettings;\n\nc"
},
{
"path": "src/Http/Middleware/SettingsPathExists.php",
"chars": 568,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Http\\Middleware;\n\n\nuse Outl1ne\\NovaSettings\\NovaSettings;\n\nclass SettingsPathExist"
},
{
"path": "src/Models/Settings.php",
"chars": 1744,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Models;\n\nuse Outl1ne\\NovaSettings\\NovaSettings;\nuse Illuminate\\Database\\Eloquent\\M"
},
{
"path": "src/Nova/Resources/Settings.php",
"chars": 532,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Nova\\Resources;\n\nuse Laravel\\Nova\\Resource;\nuse Illuminate\\Http\\Request;\nuse Outl1"
},
{
"path": "src/NovaSettings.php",
"chars": 4527,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings;\n\nuse Laravel\\Nova\\Nova;\nuse Laravel\\Nova\\Tool;\nuse Illuminate\\Support\\Str;\nuse Il"
},
{
"path": "src/NovaSettingsCacheStore.php",
"chars": 1716,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings;\n\nuse Illuminate\\Support\\Facades\\Cache;\n\nuse function collect;\nuse function is_arr"
},
{
"path": "src/NovaSettingsInMemoryStore.php",
"chars": 953,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings;\n\nuse function collect;\nuse function is_array;\nuse function is_string;\n\nclass Nova"
},
{
"path": "src/NovaSettingsNoCacheStore.php",
"chars": 382,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings;\n\nuse function is_string;\n\nclass NovaSettingsNoCacheStore extends NovaSettingsStor"
},
{
"path": "src/NovaSettingsServiceProvider.php",
"chars": 2987,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings;\n\nuse Laravel\\Nova\\Nova;\nuse Illuminate\\Support\\Facades\\Route;\nuse Illuminate\\Supp"
},
{
"path": "src/NovaSettingsStore.php",
"chars": 3975,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings;\n\nuse Illuminate\\Support\\Str;\n\nuse function array_diff;\nuse function array_keys;\nu"
},
{
"path": "src/helpers.php",
"chars": 620,
"preview": "<?php\n\nuse Outl1ne\\NovaSettings\\NovaSettings;\n\nif (!function_exists('nova_get_settings')) {\n function nova_get_settin"
},
{
"path": "testbench.yaml",
"chars": 91,
"preview": "laravel: ./vendor/laravel/nova-dusk-suite\n\nproviders:\n - Laravel\\Nova\\NovaServiceProvider\n"
},
{
"path": "tests/Browser/DetailTest.php",
"chars": 1402,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Tests\\Browser;\n\nuse App\\Models\\User;\nuse Laravel\\Dusk\\Browser;\nuse Outl1ne\\NovaSet"
},
{
"path": "tests/DuskTestCase.php",
"chars": 5689,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Tests;\n\nuse Laravel\\Nova\\Nova;\nuse Laravel\\Dusk\\Browser;\nuse Laravel\\Nova\\Fields\\T"
},
{
"path": "tests/Feature/NavigationTest.php",
"chars": 1425,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Tests\\Feature;\n\nuse Laravel\\Nova\\Fields\\Text;\nuse Outl1ne\\NovaSettings\\NovaSetting"
},
{
"path": "tests/Feature/SettingsCastTest.php",
"chars": 1468,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Tests\\Feature;\n\nuse Laravel\\Nova\\Fields\\Number;\nuse Outl1ne\\NovaSettings\\NovaSetti"
},
{
"path": "tests/Feature/SettingsHelpersTest.php",
"chars": 697,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Tests\\Feature;\n\nuse Outl1ne\\NovaSettings\\Models\\Settings;\nuse Outl1ne\\NovaSettings"
},
{
"path": "tests/Feature/SettingsRetrieveTest.php",
"chars": 1868,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Tests\\Feature;\n\nuse Laravel\\Nova\\Fields\\Text;\nuse Outl1ne\\NovaSettings\\NovaSetting"
},
{
"path": "tests/Feature/SettingsSaveTest.php",
"chars": 1139,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Tests\\Feature;\n\nuse Laravel\\Nova\\Fields\\Text;\nuse Outl1ne\\NovaSettings\\NovaSetting"
},
{
"path": "tests/IntegrationTestCase.php",
"chars": 876,
"preview": "<?php\n\nnamespace Outl1ne\\NovaSettings\\Tests;\n\nuse Laravel\\Nova\\Nova;\nuse Laravel\\Nova\\NovaServiceProvider;\nuse Illuminat"
},
{
"path": "tests/bootstrap.php",
"chars": 354,
"preview": "<?php\n\nuse Dotenv\\Dotenv;\nuse Illuminate\\Support\\Env;\n\nrequire __DIR__.'/../vendor/autoload.php';\n\nDotenv::create(\n E"
},
{
"path": "webpack.mix.js",
"chars": 357,
"preview": "let mix = require('laravel-mix');\nlet path = require('path');\n\nmix.extend('nova', new require('laravel-nova-devtool'));\n"
}
]
About this extraction
This page contains the full source code of the optimistdigital/nova-settings GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 58 files (81.9 KB), approximately 22.5k tokens, and a symbol index with 130 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.