From c1500cd8e5450d9590b779e636431d5a8bc59cf5 Mon Sep 17 00:00:00 2001 From: fatihalp Date: Tue, 3 Mar 2026 13:59:46 +0300 Subject: [PATCH] beta --- .../Filament/Pages/ManageGeneralSettings.php | 9 +- .../Filament/Resources/ListingResource.php | 59 +- .../Admin/Providers/AdminPanelProvider.php | 3 + Modules/Category/Policies/CategoryPolicy.php | 70 + .../Http/Controllers/ListingController.php | 34 +- Modules/Listing/Models/Listing.php | 30 +- Modules/Listing/Policies/ListingPolicy.php | 70 + .../Listing/Support/ListingPanelHelper.php | 55 + Modules/Location/Policies/CountryPolicy.php | 70 + .../Filament/Resources/ListingResource.php | 59 +- app/Policies/CmsCategoryPolicy.php | 70 + app/Policies/CmsCommentPolicy.php | 70 + app/Policies/CmsPagePolicy.php | 70 + app/Policies/CmsPostPolicy.php | 70 + app/Policies/MediaCollectionPolicy.php | 70 + app/Policies/RolePolicy.php | 70 + .../TallcmsContactSubmissionPolicy.php | 70 + app/Policies/TallcmsMediaPolicy.php | 70 + app/Policies/TallcmsMenuPolicy.php | 70 + app/Policies/UserPolicy.php | 67 + app/Providers/AppServiceProvider.php | 8 + app/Settings/GeneralSettings.php | 2 + app/Support/CountryCodeManager.php | 168 ++ composer.json | 5 +- config/money.php | 1843 +++++++++++++++++ config/tallcms.php | 569 +++++ config/theme.php | 8 + ...fault_country_code_to_general_settings.php | 11 + public/themes/talldaisy | 1 + 29 files changed, 3662 insertions(+), 109 deletions(-) create mode 100644 Modules/Category/Policies/CategoryPolicy.php create mode 100644 Modules/Listing/Policies/ListingPolicy.php create mode 100644 Modules/Listing/Support/ListingPanelHelper.php create mode 100644 Modules/Location/Policies/CountryPolicy.php create mode 100644 app/Policies/CmsCategoryPolicy.php create mode 100644 app/Policies/CmsCommentPolicy.php create mode 100644 app/Policies/CmsPagePolicy.php create mode 100644 app/Policies/CmsPostPolicy.php create mode 100644 app/Policies/MediaCollectionPolicy.php create mode 100644 app/Policies/RolePolicy.php create mode 100644 app/Policies/TallcmsContactSubmissionPolicy.php create mode 100644 app/Policies/TallcmsMediaPolicy.php create mode 100644 app/Policies/TallcmsMenuPolicy.php create mode 100644 app/Policies/UserPolicy.php create mode 100644 app/Support/CountryCodeManager.php create mode 100644 config/money.php create mode 100644 config/tallcms.php create mode 100644 config/theme.php create mode 100644 database/settings/2026_03_03_170000_add_default_country_code_to_general_settings.php create mode 120000 public/themes/talldaisy diff --git a/Modules/Admin/Filament/Pages/ManageGeneralSettings.php b/Modules/Admin/Filament/Pages/ManageGeneralSettings.php index cbba4a139..a67737e39 100644 --- a/Modules/Admin/Filament/Pages/ManageGeneralSettings.php +++ b/Modules/Admin/Filament/Pages/ManageGeneralSettings.php @@ -2,6 +2,7 @@ namespace Modules\Admin\Filament\Pages; +use App\Support\CountryCodeManager; use App\Settings\GeneralSettings; use BackedEnum; use Filament\Forms\Components\FileUpload; @@ -12,6 +13,7 @@ use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Toggle; use Filament\Pages\SettingsPage; use Filament\Schemas\Schema; +use Tapp\FilamentCountryCodeField\Forms\Components\CountryCodeSelect; use UnitEnum; use Ysfkaya\FilamentPhoneInput\Forms\PhoneInput; @@ -61,6 +63,11 @@ class ManageGeneralSettings extends SettingsPage ->options($this->localeOptions()) ->required() ->searchable(), + CountryCodeSelect::make('default_country_code') + ->label('Default Country') + ->default('+90') + ->required() + ->helperText('Used as default country in panel forms.'), TagsInput::make('currencies') ->label('Currencies') ->placeholder('USD') @@ -81,7 +88,7 @@ class ManageGeneralSettings extends SettingsPage ->maxLength(255), PhoneInput::make('whatsapp') ->label('WhatsApp') - ->defaultCountry('TR') + ->defaultCountry(CountryCodeManager::defaultCountryIso2()) ->nullable() ->formatAsYouType() ->helperText('Use international format, e.g. +905551112233.'), diff --git a/Modules/Admin/Filament/Resources/ListingResource.php b/Modules/Admin/Filament/Resources/ListingResource.php index d9c13f188..8292f52bb 100644 --- a/Modules/Admin/Filament/Resources/ListingResource.php +++ b/Modules/Admin/Filament/Resources/ListingResource.php @@ -4,7 +4,7 @@ namespace Modules\Admin\Filament\Resources; use A909M\FilamentStateFusion\Forms\Components\StateFusionSelect; use A909M\FilamentStateFusion\Tables\Columns\StateFusionSelectColumn; use A909M\FilamentStateFusion\Tables\Filters\StateFusionSelectFilter; -use App\Settings\GeneralSettings; +use App\Support\CountryCodeManager; use BackedEnum; use Cheesegrits\FilamentGoogleMaps\Fields\Map; use Filament\Actions\Action; @@ -24,7 +24,8 @@ use Filament\Tables\Table; use Modules\Admin\Filament\Resources\ListingResource\Pages; use Modules\Category\Models\Category; use Modules\Listing\Models\Listing; -use Throwable; +use Modules\Listing\Support\ListingPanelHelper; +use Tapp\FilamentCountryCodeField\Forms\Components\CountryCodeSelect; use UnitEnum; use Ysfkaya\FilamentPhoneInput\Forms\PhoneInput; @@ -40,28 +41,33 @@ class ListingResource extends Resource TextInput::make('title')->required()->maxLength(255)->live(onBlur: true)->afterStateUpdated(fn ($state, $set) => $set('slug', \Illuminate\Support\Str::slug($state) . '-' . \Illuminate\Support\Str::random(4))), TextInput::make('slug')->required()->maxLength(255)->unique(ignoreRecord: true), Textarea::make('description')->rows(4), - TextInput::make('price')->numeric()->prefix('$'), + TextInput::make('price') + ->numeric() + ->currencyMask(thousandSeparator: ',', decimalSeparator: '.', precision: 2), Select::make('currency') - ->options(fn () => self::currencyOptions()) - ->default(fn () => self::defaultCurrency()) + ->options(fn () => ListingPanelHelper::currencyOptions()) + ->default(fn () => ListingPanelHelper::defaultCurrency()) ->required(), Select::make('category_id')->label('Category')->options(fn () => Category::where('is_active', true)->pluck('name', 'id'))->searchable()->nullable(), StateFusionSelect::make('status')->required(), - PhoneInput::make('contact_phone')->defaultCountry('TR')->nullable(), + PhoneInput::make('contact_phone')->defaultCountry(CountryCodeManager::defaultCountryIso2())->nullable(), TextInput::make('contact_email')->email()->maxLength(255), Toggle::make('is_featured')->default(false), TextInput::make('city')->maxLength(100), - TextInput::make('country')->maxLength(100), + CountryCodeSelect::make('country') + ->label('Country') + ->default(fn () => CountryCodeManager::defaultCountryCode()) + ->formatStateUsing(fn ($state): ?string => CountryCodeManager::countryCodeFromLabelOrCode($state)) + ->dehydrateStateUsing(fn ($state, ?Listing $record): ?string => CountryCodeManager::normalizeStoredCountry($state ?? $record?->country)), Map::make('location') ->label('Location') - ->visible(fn (): bool => self::googleMapsEnabled()) + ->visible(fn (): bool => ListingPanelHelper::googleMapsEnabled()) ->draggable() ->clickable() ->autocomplete('city') ->autocompleteReverse(true) ->reverseGeocode([ 'city' => '%L', - 'country' => '%C', ]) ->defaultLocation([41.0082, 28.9784]) ->defaultZoom(10) @@ -84,7 +90,9 @@ class ListingResource extends Resource TextColumn::make('id')->sortable(), TextColumn::make('title')->searchable()->sortable()->limit(40), TextColumn::make('category.name')->label('Category'), - TextColumn::make('price')->money('USD')->sortable(), + TextColumn::make('price') + ->currency(fn (Listing $record): string => $record->currency ?: ListingPanelHelper::defaultCurrency()) + ->sortable(), StateFusionSelectColumn::make('status'), IconColumn::make('is_featured')->boolean()->label('Featured'), TextColumn::make('city'), @@ -109,35 +117,4 @@ class ListingResource extends Resource 'edit' => Pages\EditListing::route('/{record}/edit'), ]; } - - private static function currencyOptions(): array - { - $codes = collect(config('app.currencies', ['USD'])) - ->filter(fn ($code) => is_string($code) && trim($code) !== '') - ->map(fn (string $code) => strtoupper(substr(trim($code), 0, 3))) - ->filter(fn (string $code) => strlen($code) === 3) - ->unique() - ->values() - ->all(); - - if ($codes === []) { - $codes = ['USD']; - } - - return collect($codes)->mapWithKeys(fn (string $code) => [$code => $code])->all(); - } - - private static function defaultCurrency(): string - { - return array_key_first(self::currencyOptions()) ?? 'USD'; - } - - private static function googleMapsEnabled(): bool - { - try { - return (bool) app(GeneralSettings::class)->enable_google_maps; - } catch (Throwable) { - return false; - } - } } diff --git a/Modules/Admin/Providers/AdminPanelProvider.php b/Modules/Admin/Providers/AdminPanelProvider.php index a274398c4..7dc59c0bd 100644 --- a/Modules/Admin/Providers/AdminPanelProvider.php +++ b/Modules/Admin/Providers/AdminPanelProvider.php @@ -24,6 +24,7 @@ use Modules\Admin\Filament\Resources\CategoryResource; use Modules\Admin\Filament\Resources\ListingResource; use Modules\Admin\Filament\Resources\LocationResource; use Modules\Admin\Filament\Resources\UserResource; +use TallCms\Cms\TallCmsPlugin; class AdminPanelProvider extends PanelProvider { @@ -40,6 +41,8 @@ class AdminPanelProvider extends PanelProvider ->discoverWidgets(in: module_path('Admin', 'Filament/Widgets'), for: 'Modules\\Admin\\Filament\\Widgets') ->plugins([ FilamentStateFusionPlugin::make(), + TallCmsPlugin::make() + ->withoutUsers(), BreezyCore::make() ->myProfile( shouldRegisterNavigation: true, diff --git a/Modules/Category/Policies/CategoryPolicy.php b/Modules/Category/Policies/CategoryPolicy.php new file mode 100644 index 000000000..b8ca9cb74 --- /dev/null +++ b/Modules/Category/Policies/CategoryPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:Category'); + } + + public function view(AuthUser $authUser, Category $category): bool + { + return $authUser->can('View:Category'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:Category'); + } + + public function update(AuthUser $authUser, Category $category): bool + { + return $authUser->can('Update:Category'); + } + + public function delete(AuthUser $authUser, Category $category): bool + { + return $authUser->can('Delete:Category'); + } + + public function restore(AuthUser $authUser, Category $category): bool + { + return $authUser->can('Restore:Category'); + } + + public function forceDelete(AuthUser $authUser, Category $category): bool + { + return $authUser->can('ForceDelete:Category'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:Category'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:Category'); + } + + public function replicate(AuthUser $authUser, Category $category): bool + { + return $authUser->can('Replicate:Category'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:Category'); + } + +} \ No newline at end of file diff --git a/Modules/Listing/Http/Controllers/ListingController.php b/Modules/Listing/Http/Controllers/ListingController.php index 92f13a10a..a3be4b4a0 100644 --- a/Modules/Listing/Http/Controllers/ListingController.php +++ b/Modules/Listing/Http/Controllers/ListingController.php @@ -1,18 +1,18 @@ orderByDesc('is_featured') - ->orderByDesc('created_at') + $listings = Listing::query() + ->publicFeed() ->paginate(12); return view('listing::index', compact('listings')); } @@ -25,13 +25,13 @@ class ListingController extends Controller public function create() { return view('listing::create', [ - 'currencies' => $this->currencyCodes(), + 'currencies' => ListingPanelHelper::currencyCodes(), ]); } public function store(Request $request) { - $currencies = $this->currencyCodes(); + $currencies = ListingPanelHelper::currencyCodes(); $data = $request->validate([ 'title' => 'required|string|min:3|max:255', @@ -44,23 +44,9 @@ class ListingController extends Controller 'contact_email' => 'nullable|email', 'contact_phone' => 'nullable|string', ]); - $data['user_id'] = auth()->id(); - $data['currency'] = strtoupper($data['currency'] ?? $currencies[0]); - $data['slug'] = \Illuminate\Support\Str::slug($data['title']) . '-' . \Illuminate\Support\Str::random(6); - $listing = Listing::create($data); + + $listing = Listing::createFromFrontend($data, auth()->id()); + return redirect()->route('listings.show', $listing)->with('success', 'Listing created!'); } - - private function currencyCodes(): array - { - $codes = collect(config('app.currencies', ['USD'])) - ->filter(fn ($code) => is_string($code) && trim($code) !== '') - ->map(fn (string $code) => strtoupper(substr(trim($code), 0, 3))) - ->filter(fn (string $code) => strlen($code) === 3) - ->unique() - ->values() - ->all(); - - return $codes !== [] ? $codes : ['USD']; - } } diff --git a/Modules/Listing/Models/Listing.php b/Modules/Listing/Models/Listing.php index 5ba630bef..0139634b2 100644 --- a/Modules/Listing/Models/Listing.php +++ b/Modules/Listing/Models/Listing.php @@ -2,9 +2,12 @@ namespace Modules\Listing\Models; use Illuminate\Database\Eloquent\Casts\Attribute; -use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Str; use Modules\Listing\States\ListingStatus; +use Modules\Listing\Support\ListingPanelHelper; use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\Traits\LogsActivity; use Spatie\MediaLibrary\HasMedia; @@ -52,6 +55,31 @@ class Listing extends Model implements HasMedia return $this->belongsTo(\App\Models\User::class); } + public function scopePublicFeed(Builder $query): Builder + { + return $query + ->where('status', 'active') + ->orderByDesc('is_featured') + ->orderByDesc('created_at'); + } + + public static function createFromFrontend(array $data, null | int | string $userId): self + { + $baseSlug = Str::slug((string) ($data['title'] ?? 'listing')); + $baseSlug = $baseSlug !== '' ? $baseSlug : 'listing'; + + do { + $slug = $baseSlug.'-'.Str::random(6); + } while (static::query()->where('slug', $slug)->exists()); + + $payload = $data; + $payload['user_id'] = $userId; + $payload['currency'] = ListingPanelHelper::normalizeCurrency($data['currency'] ?? null); + $payload['slug'] = $slug; + + return static::query()->create($payload); + } + public function registerMediaCollections(): void { $this->addMediaCollection('listing-images'); diff --git a/Modules/Listing/Policies/ListingPolicy.php b/Modules/Listing/Policies/ListingPolicy.php new file mode 100644 index 000000000..9f21e044f --- /dev/null +++ b/Modules/Listing/Policies/ListingPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:Listing'); + } + + public function view(AuthUser $authUser, Listing $listing): bool + { + return $authUser->can('View:Listing'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:Listing'); + } + + public function update(AuthUser $authUser, Listing $listing): bool + { + return $authUser->can('Update:Listing'); + } + + public function delete(AuthUser $authUser, Listing $listing): bool + { + return $authUser->can('Delete:Listing'); + } + + public function restore(AuthUser $authUser, Listing $listing): bool + { + return $authUser->can('Restore:Listing'); + } + + public function forceDelete(AuthUser $authUser, Listing $listing): bool + { + return $authUser->can('ForceDelete:Listing'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:Listing'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:Listing'); + } + + public function replicate(AuthUser $authUser, Listing $listing): bool + { + return $authUser->can('Replicate:Listing'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:Listing'); + } + +} \ No newline at end of file diff --git a/Modules/Listing/Support/ListingPanelHelper.php b/Modules/Listing/Support/ListingPanelHelper.php new file mode 100644 index 000000000..bdba42345 --- /dev/null +++ b/Modules/Listing/Support/ListingPanelHelper.php @@ -0,0 +1,55 @@ +filter(fn ($code) => is_string($code) && trim($code) !== '') + ->map(fn (string $code) => strtoupper(substr(trim($code), 0, 3))) + ->filter(fn (string $code) => strlen($code) === 3) + ->unique() + ->values() + ->all(); + + return $codes !== [] ? $codes : ['USD']; + } + + public static function currencyOptions(): array + { + return collect(self::currencyCodes()) + ->mapWithKeys(fn (string $code) => [$code => $code]) + ->all(); + } + + public static function defaultCurrency(): string + { + return self::currencyCodes()[0] ?? 'USD'; + } + + public static function normalizeCurrency(null | string $currency): string + { + $normalized = strtoupper(substr(trim((string) $currency), 0, 3)); + $codes = self::currencyCodes(); + + if (in_array($normalized, $codes, true)) { + return $normalized; + } + + return self::defaultCurrency(); + } + + public static function googleMapsEnabled(): bool + { + try { + return (bool) app(GeneralSettings::class)->enable_google_maps; + } catch (Throwable) { + return false; + } + } +} diff --git a/Modules/Location/Policies/CountryPolicy.php b/Modules/Location/Policies/CountryPolicy.php new file mode 100644 index 000000000..9478c17c7 --- /dev/null +++ b/Modules/Location/Policies/CountryPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:Country'); + } + + public function view(AuthUser $authUser, Country $country): bool + { + return $authUser->can('View:Country'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:Country'); + } + + public function update(AuthUser $authUser, Country $country): bool + { + return $authUser->can('Update:Country'); + } + + public function delete(AuthUser $authUser, Country $country): bool + { + return $authUser->can('Delete:Country'); + } + + public function restore(AuthUser $authUser, Country $country): bool + { + return $authUser->can('Restore:Country'); + } + + public function forceDelete(AuthUser $authUser, Country $country): bool + { + return $authUser->can('ForceDelete:Country'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:Country'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:Country'); + } + + public function replicate(AuthUser $authUser, Country $country): bool + { + return $authUser->can('Replicate:Country'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:Country'); + } + +} \ No newline at end of file diff --git a/Modules/Partner/Filament/Resources/ListingResource.php b/Modules/Partner/Filament/Resources/ListingResource.php index 6ad878f1d..79045e570 100644 --- a/Modules/Partner/Filament/Resources/ListingResource.php +++ b/Modules/Partner/Filament/Resources/ListingResource.php @@ -4,7 +4,7 @@ namespace Modules\Partner\Filament\Resources; use A909M\FilamentStateFusion\Forms\Components\StateFusionSelect; use A909M\FilamentStateFusion\Tables\Columns\StateFusionSelectColumn; use A909M\FilamentStateFusion\Tables\Filters\StateFusionSelectFilter; -use App\Settings\GeneralSettings; +use App\Support\CountryCodeManager; use BackedEnum; use Cheesegrits\FilamentGoogleMaps\Fields\Map; use Filament\Actions\Action; @@ -23,8 +23,9 @@ use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; use Modules\Category\Models\Category; use Modules\Listing\Models\Listing; +use Modules\Listing\Support\ListingPanelHelper; use Modules\Partner\Filament\Resources\ListingResource\Pages; -use Throwable; +use Tapp\FilamentCountryCodeField\Forms\Components\CountryCodeSelect; use Ysfkaya\FilamentPhoneInput\Forms\PhoneInput; class ListingResource extends Resource @@ -38,27 +39,32 @@ class ListingResource extends Resource TextInput::make('title')->required()->maxLength(255)->live(onBlur: true)->afterStateUpdated(fn ($state, $set) => $set('slug', \Illuminate\Support\Str::slug($state) . '-' . \Illuminate\Support\Str::random(4))), TextInput::make('slug')->required()->maxLength(255)->unique(ignoreRecord: true), Textarea::make('description')->rows(4), - TextInput::make('price')->numeric()->prefix('$'), + TextInput::make('price') + ->numeric() + ->currencyMask(thousandSeparator: ',', decimalSeparator: '.', precision: 2), Select::make('currency') - ->options(fn () => self::currencyOptions()) - ->default(fn () => self::defaultCurrency()) + ->options(fn () => ListingPanelHelper::currencyOptions()) + ->default(fn () => ListingPanelHelper::defaultCurrency()) ->required(), Select::make('category_id')->label('Category')->options(fn () => Category::where('is_active', true)->pluck('name', 'id'))->searchable()->nullable(), StateFusionSelect::make('status')->required(), - PhoneInput::make('contact_phone')->defaultCountry('TR')->nullable(), + PhoneInput::make('contact_phone')->defaultCountry(CountryCodeManager::defaultCountryIso2())->nullable(), TextInput::make('contact_email')->email()->maxLength(255), TextInput::make('city')->maxLength(100), - TextInput::make('country')->maxLength(100), + CountryCodeSelect::make('country') + ->label('Country') + ->default(fn () => CountryCodeManager::defaultCountryCode()) + ->formatStateUsing(fn ($state): ?string => CountryCodeManager::countryCodeFromLabelOrCode($state)) + ->dehydrateStateUsing(fn ($state, ?Listing $record): ?string => CountryCodeManager::normalizeStoredCountry($state ?? $record?->country)), Map::make('location') ->label('Location') - ->visible(fn (): bool => self::googleMapsEnabled()) + ->visible(fn (): bool => ListingPanelHelper::googleMapsEnabled()) ->draggable() ->clickable() ->autocomplete('city') ->autocompleteReverse(true) ->reverseGeocode([ 'city' => '%L', - 'country' => '%C', ]) ->defaultLocation([41.0082, 28.9784]) ->defaultZoom(10) @@ -80,7 +86,9 @@ class ListingResource extends Resource ->circular(), TextColumn::make('title')->searchable()->sortable()->limit(40), TextColumn::make('category.name')->label('Category'), - TextColumn::make('price')->money('USD')->sortable(), + TextColumn::make('price') + ->currency(fn (Listing $record): string => $record->currency ?: ListingPanelHelper::defaultCurrency()) + ->sortable(), StateFusionSelectColumn::make('status'), TextColumn::make('city'), TextColumn::make('created_at')->dateTime()->sortable(), @@ -109,35 +117,4 @@ class ListingResource extends Resource 'edit' => Pages\EditListing::route('/{record}/edit'), ]; } - - private static function currencyOptions(): array - { - $codes = collect(config('app.currencies', ['USD'])) - ->filter(fn ($code) => is_string($code) && trim($code) !== '') - ->map(fn (string $code) => strtoupper(substr(trim($code), 0, 3))) - ->filter(fn (string $code) => strlen($code) === 3) - ->unique() - ->values() - ->all(); - - if ($codes === []) { - $codes = ['USD']; - } - - return collect($codes)->mapWithKeys(fn (string $code) => [$code => $code])->all(); - } - - private static function defaultCurrency(): string - { - return array_key_first(self::currencyOptions()) ?? 'USD'; - } - - private static function googleMapsEnabled(): bool - { - try { - return (bool) app(GeneralSettings::class)->enable_google_maps; - } catch (Throwable) { - return false; - } - } } diff --git a/app/Policies/CmsCategoryPolicy.php b/app/Policies/CmsCategoryPolicy.php new file mode 100644 index 000000000..241735ab6 --- /dev/null +++ b/app/Policies/CmsCategoryPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:CmsCategory'); + } + + public function view(AuthUser $authUser, CmsCategory $cmsCategory): bool + { + return $authUser->can('View:CmsCategory'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:CmsCategory'); + } + + public function update(AuthUser $authUser, CmsCategory $cmsCategory): bool + { + return $authUser->can('Update:CmsCategory'); + } + + public function delete(AuthUser $authUser, CmsCategory $cmsCategory): bool + { + return $authUser->can('Delete:CmsCategory'); + } + + public function restore(AuthUser $authUser, CmsCategory $cmsCategory): bool + { + return $authUser->can('Restore:CmsCategory'); + } + + public function forceDelete(AuthUser $authUser, CmsCategory $cmsCategory): bool + { + return $authUser->can('ForceDelete:CmsCategory'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:CmsCategory'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:CmsCategory'); + } + + public function replicate(AuthUser $authUser, CmsCategory $cmsCategory): bool + { + return $authUser->can('Replicate:CmsCategory'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:CmsCategory'); + } + +} \ No newline at end of file diff --git a/app/Policies/CmsCommentPolicy.php b/app/Policies/CmsCommentPolicy.php new file mode 100644 index 000000000..bfd5c148c --- /dev/null +++ b/app/Policies/CmsCommentPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:CmsComment'); + } + + public function view(AuthUser $authUser, CmsComment $cmsComment): bool + { + return $authUser->can('View:CmsComment'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:CmsComment'); + } + + public function update(AuthUser $authUser, CmsComment $cmsComment): bool + { + return $authUser->can('Update:CmsComment'); + } + + public function delete(AuthUser $authUser, CmsComment $cmsComment): bool + { + return $authUser->can('Delete:CmsComment'); + } + + public function restore(AuthUser $authUser, CmsComment $cmsComment): bool + { + return $authUser->can('Restore:CmsComment'); + } + + public function forceDelete(AuthUser $authUser, CmsComment $cmsComment): bool + { + return $authUser->can('ForceDelete:CmsComment'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:CmsComment'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:CmsComment'); + } + + public function replicate(AuthUser $authUser, CmsComment $cmsComment): bool + { + return $authUser->can('Replicate:CmsComment'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:CmsComment'); + } + +} \ No newline at end of file diff --git a/app/Policies/CmsPagePolicy.php b/app/Policies/CmsPagePolicy.php new file mode 100644 index 000000000..2c3f555f1 --- /dev/null +++ b/app/Policies/CmsPagePolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:CmsPage'); + } + + public function view(AuthUser $authUser, CmsPage $cmsPage): bool + { + return $authUser->can('View:CmsPage'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:CmsPage'); + } + + public function update(AuthUser $authUser, CmsPage $cmsPage): bool + { + return $authUser->can('Update:CmsPage'); + } + + public function delete(AuthUser $authUser, CmsPage $cmsPage): bool + { + return $authUser->can('Delete:CmsPage'); + } + + public function restore(AuthUser $authUser, CmsPage $cmsPage): bool + { + return $authUser->can('Restore:CmsPage'); + } + + public function forceDelete(AuthUser $authUser, CmsPage $cmsPage): bool + { + return $authUser->can('ForceDelete:CmsPage'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:CmsPage'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:CmsPage'); + } + + public function replicate(AuthUser $authUser, CmsPage $cmsPage): bool + { + return $authUser->can('Replicate:CmsPage'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:CmsPage'); + } + +} \ No newline at end of file diff --git a/app/Policies/CmsPostPolicy.php b/app/Policies/CmsPostPolicy.php new file mode 100644 index 000000000..163b01fc3 --- /dev/null +++ b/app/Policies/CmsPostPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:CmsPost'); + } + + public function view(AuthUser $authUser, CmsPost $cmsPost): bool + { + return $authUser->can('View:CmsPost'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:CmsPost'); + } + + public function update(AuthUser $authUser, CmsPost $cmsPost): bool + { + return $authUser->can('Update:CmsPost'); + } + + public function delete(AuthUser $authUser, CmsPost $cmsPost): bool + { + return $authUser->can('Delete:CmsPost'); + } + + public function restore(AuthUser $authUser, CmsPost $cmsPost): bool + { + return $authUser->can('Restore:CmsPost'); + } + + public function forceDelete(AuthUser $authUser, CmsPost $cmsPost): bool + { + return $authUser->can('ForceDelete:CmsPost'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:CmsPost'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:CmsPost'); + } + + public function replicate(AuthUser $authUser, CmsPost $cmsPost): bool + { + return $authUser->can('Replicate:CmsPost'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:CmsPost'); + } + +} \ No newline at end of file diff --git a/app/Policies/MediaCollectionPolicy.php b/app/Policies/MediaCollectionPolicy.php new file mode 100644 index 000000000..6c34fa853 --- /dev/null +++ b/app/Policies/MediaCollectionPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:MediaCollection'); + } + + public function view(AuthUser $authUser, MediaCollection $mediaCollection): bool + { + return $authUser->can('View:MediaCollection'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:MediaCollection'); + } + + public function update(AuthUser $authUser, MediaCollection $mediaCollection): bool + { + return $authUser->can('Update:MediaCollection'); + } + + public function delete(AuthUser $authUser, MediaCollection $mediaCollection): bool + { + return $authUser->can('Delete:MediaCollection'); + } + + public function restore(AuthUser $authUser, MediaCollection $mediaCollection): bool + { + return $authUser->can('Restore:MediaCollection'); + } + + public function forceDelete(AuthUser $authUser, MediaCollection $mediaCollection): bool + { + return $authUser->can('ForceDelete:MediaCollection'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:MediaCollection'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:MediaCollection'); + } + + public function replicate(AuthUser $authUser, MediaCollection $mediaCollection): bool + { + return $authUser->can('Replicate:MediaCollection'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:MediaCollection'); + } + +} \ No newline at end of file diff --git a/app/Policies/RolePolicy.php b/app/Policies/RolePolicy.php new file mode 100644 index 000000000..a2a3263ef --- /dev/null +++ b/app/Policies/RolePolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:Role'); + } + + public function view(AuthUser $authUser, Role $role): bool + { + return $authUser->can('View:Role'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:Role'); + } + + public function update(AuthUser $authUser, Role $role): bool + { + return $authUser->can('Update:Role'); + } + + public function delete(AuthUser $authUser, Role $role): bool + { + return $authUser->can('Delete:Role'); + } + + public function restore(AuthUser $authUser, Role $role): bool + { + return $authUser->can('Restore:Role'); + } + + public function forceDelete(AuthUser $authUser, Role $role): bool + { + return $authUser->can('ForceDelete:Role'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:Role'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:Role'); + } + + public function replicate(AuthUser $authUser, Role $role): bool + { + return $authUser->can('Replicate:Role'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:Role'); + } + +} \ No newline at end of file diff --git a/app/Policies/TallcmsContactSubmissionPolicy.php b/app/Policies/TallcmsContactSubmissionPolicy.php new file mode 100644 index 000000000..627faab3c --- /dev/null +++ b/app/Policies/TallcmsContactSubmissionPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:TallcmsContactSubmission'); + } + + public function view(AuthUser $authUser, TallcmsContactSubmission $tallcmsContactSubmission): bool + { + return $authUser->can('View:TallcmsContactSubmission'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:TallcmsContactSubmission'); + } + + public function update(AuthUser $authUser, TallcmsContactSubmission $tallcmsContactSubmission): bool + { + return $authUser->can('Update:TallcmsContactSubmission'); + } + + public function delete(AuthUser $authUser, TallcmsContactSubmission $tallcmsContactSubmission): bool + { + return $authUser->can('Delete:TallcmsContactSubmission'); + } + + public function restore(AuthUser $authUser, TallcmsContactSubmission $tallcmsContactSubmission): bool + { + return $authUser->can('Restore:TallcmsContactSubmission'); + } + + public function forceDelete(AuthUser $authUser, TallcmsContactSubmission $tallcmsContactSubmission): bool + { + return $authUser->can('ForceDelete:TallcmsContactSubmission'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:TallcmsContactSubmission'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:TallcmsContactSubmission'); + } + + public function replicate(AuthUser $authUser, TallcmsContactSubmission $tallcmsContactSubmission): bool + { + return $authUser->can('Replicate:TallcmsContactSubmission'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:TallcmsContactSubmission'); + } + +} \ No newline at end of file diff --git a/app/Policies/TallcmsMediaPolicy.php b/app/Policies/TallcmsMediaPolicy.php new file mode 100644 index 000000000..509c12e15 --- /dev/null +++ b/app/Policies/TallcmsMediaPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:TallcmsMedia'); + } + + public function view(AuthUser $authUser, TallcmsMedia $tallcmsMedia): bool + { + return $authUser->can('View:TallcmsMedia'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:TallcmsMedia'); + } + + public function update(AuthUser $authUser, TallcmsMedia $tallcmsMedia): bool + { + return $authUser->can('Update:TallcmsMedia'); + } + + public function delete(AuthUser $authUser, TallcmsMedia $tallcmsMedia): bool + { + return $authUser->can('Delete:TallcmsMedia'); + } + + public function restore(AuthUser $authUser, TallcmsMedia $tallcmsMedia): bool + { + return $authUser->can('Restore:TallcmsMedia'); + } + + public function forceDelete(AuthUser $authUser, TallcmsMedia $tallcmsMedia): bool + { + return $authUser->can('ForceDelete:TallcmsMedia'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:TallcmsMedia'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:TallcmsMedia'); + } + + public function replicate(AuthUser $authUser, TallcmsMedia $tallcmsMedia): bool + { + return $authUser->can('Replicate:TallcmsMedia'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:TallcmsMedia'); + } + +} \ No newline at end of file diff --git a/app/Policies/TallcmsMenuPolicy.php b/app/Policies/TallcmsMenuPolicy.php new file mode 100644 index 000000000..464eadbf4 --- /dev/null +++ b/app/Policies/TallcmsMenuPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:TallcmsMenu'); + } + + public function view(AuthUser $authUser, TallcmsMenu $tallcmsMenu): bool + { + return $authUser->can('View:TallcmsMenu'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:TallcmsMenu'); + } + + public function update(AuthUser $authUser, TallcmsMenu $tallcmsMenu): bool + { + return $authUser->can('Update:TallcmsMenu'); + } + + public function delete(AuthUser $authUser, TallcmsMenu $tallcmsMenu): bool + { + return $authUser->can('Delete:TallcmsMenu'); + } + + public function restore(AuthUser $authUser, TallcmsMenu $tallcmsMenu): bool + { + return $authUser->can('Restore:TallcmsMenu'); + } + + public function forceDelete(AuthUser $authUser, TallcmsMenu $tallcmsMenu): bool + { + return $authUser->can('ForceDelete:TallcmsMenu'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:TallcmsMenu'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:TallcmsMenu'); + } + + public function replicate(AuthUser $authUser, TallcmsMenu $tallcmsMenu): bool + { + return $authUser->can('Replicate:TallcmsMenu'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:TallcmsMenu'); + } + +} \ No newline at end of file diff --git a/app/Policies/UserPolicy.php b/app/Policies/UserPolicy.php new file mode 100644 index 000000000..10cdce3e3 --- /dev/null +++ b/app/Policies/UserPolicy.php @@ -0,0 +1,67 @@ +can('ViewAny:User'); + } + + public function view(AuthUser $authUser): bool + { + return $authUser->can('View:User'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:User'); + } + + public function update(AuthUser $authUser): bool + { + return $authUser->can('Update:User'); + } + + public function delete(AuthUser $authUser): bool + { + return $authUser->can('Delete:User'); + } + + public function restore(AuthUser $authUser): bool + { + return $authUser->can('Restore:User'); + } + + public function forceDelete(AuthUser $authUser): bool + { + return $authUser->can('ForceDelete:User'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:User'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:User'); + } + + public function replicate(AuthUser $authUser): bool + { + return $authUser->can('Replicate:User'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:User'); + } + +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 23320f1f6..a3d31386c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use App\Support\CountryCodeManager; use App\Settings\GeneralSettings; use BezhanSalleh\LanguageSwitch\LanguageSwitch; use Illuminate\Support\ServiceProvider; @@ -32,12 +33,14 @@ class AppServiceProvider extends ServiceProvider $fallbackFacebookClientSecret = env('FACEBOOK_CLIENT_SECRET'); $fallbackAppleClientId = env('APPLE_CLIENT_ID'); $fallbackAppleClientSecret = env('APPLE_CLIENT_SECRET'); + $fallbackDefaultCountryCode = '+90'; $generalSettings = [ 'site_name' => $fallbackName, 'site_description' => $fallbackDescription, 'site_logo_url' => null, 'default_language' => $fallbackLocale, + 'default_country_code' => $fallbackDefaultCountryCode, 'currencies' => $fallbackCurrencies, 'sender_email' => config('mail.from.address', 'hello@example.com'), 'sender_name' => config('mail.from.name', $fallbackName), @@ -81,6 +84,7 @@ class AppServiceProvider extends ServiceProvider $facebookClientSecret = trim((string) ($settings->facebook_client_secret ?: $fallbackFacebookClientSecret)); $appleClientId = trim((string) ($settings->apple_client_id ?: $fallbackAppleClientId)); $appleClientSecret = trim((string) ($settings->apple_client_secret ?: $fallbackAppleClientSecret)); + $defaultCountryCode = CountryCodeManager::normalizeCountryCode($settings->default_country_code ?? $fallbackDefaultCountryCode); $generalSettings = [ 'site_name' => trim((string) ($settings->site_name ?: $fallbackName)), @@ -89,6 +93,7 @@ class AppServiceProvider extends ServiceProvider ? Storage::disk('public')->url($settings->site_logo) : null, 'default_language' => $defaultLanguage, + 'default_country_code' => $defaultCountryCode, 'currencies' => $currencies, 'sender_email' => trim((string) ($settings->sender_email ?: config('mail.from.address'))), 'sender_name' => trim((string) ($settings->sender_name ?: $fallbackName)), @@ -143,6 +148,9 @@ class AppServiceProvider extends ServiceProvider 'services.apple.redirect' => url('/oauth/callback/apple'), 'services.apple.stateless' => true, 'services.apple.enabled' => (bool) $generalSettings['apple_login_enabled'], + 'money.defaults.currency' => $generalSettings['currencies'][0] ?? 'USD', + 'app.default_country_code' => $generalSettings['default_country_code'] ?? $fallbackDefaultCountryCode, + 'app.default_country_iso2' => CountryCodeManager::iso2FromCountryCode($generalSettings['default_country_code'] ?? $fallbackDefaultCountryCode) ?? 'TR', ]); Event::listen(function (SocialiteWasCalled $event): void { diff --git a/app/Settings/GeneralSettings.php b/app/Settings/GeneralSettings.php index dbec6ab4d..f2107fd56 100644 --- a/app/Settings/GeneralSettings.php +++ b/app/Settings/GeneralSettings.php @@ -14,6 +14,8 @@ class GeneralSettings extends Settings public string $default_language; + public string $default_country_code; + public array $currencies; public string $sender_email; diff --git a/app/Support/CountryCodeManager.php b/app/Support/CountryCodeManager.php new file mode 100644 index 000000000..2b46385b7 --- /dev/null +++ b/app/Support/CountryCodeManager.php @@ -0,0 +1,168 @@ +contains(fn (array $country): bool => $country['country_code'] === trim((string) $value)); + } + + public static function countryCodeFromIso2(null | string $iso2): ?string + { + $iso2 = strtoupper(trim((string) $iso2)); + + if ($iso2 === '') { + return null; + } + + return self::countries() + ->first(fn (array $country): bool => $country['iso2'] === $iso2)['country_code'] ?? null; + } + + public static function iso2FromCountryCode(null | string $countryCode): ?string + { + $countryCode = trim((string) $countryCode); + + if ($countryCode === '') { + return null; + } + + return self::countries() + ->first(fn (array $country): bool => $country['country_code'] === $countryCode)['iso2'] ?? null; + } + + public static function labelFromCountryCode(null | string $countryCode): ?string + { + $countryCode = trim((string) $countryCode); + + if ($countryCode === '') { + return null; + } + + return self::countries() + ->first(fn (array $country): bool => $country['country_code'] === $countryCode)['english_label'] ?? null; + } + + public static function countryCodeFromLabelOrCode(null | string $value): ?string + { + $value = trim((string) $value); + + if ($value === '') { + return null; + } + + if (self::isValidCountryCode($value)) { + return $value; + } + + $fromIso = self::countryCodeFromIso2($value); + + if ($fromIso) { + return $fromIso; + } + + $normalizedInput = self::normalizeLabel($value); + + return self::countries() + ->first(function (array $country) use ($normalizedInput): bool { + $normalizedLabel = self::normalizeLabel($country['label']); + $normalizedEnglish = self::normalizeLabel($country['english_label']); + + return $normalizedInput === $normalizedLabel + || $normalizedInput === $normalizedEnglish + || str_contains($normalizedLabel, $normalizedInput) + || str_contains($normalizedEnglish, $normalizedInput) + || str_contains($normalizedInput, $normalizedLabel) + || str_contains($normalizedInput, $normalizedEnglish); + })['country_code'] ?? null; + } + + public static function normalizeStoredCountry(null | string $value): ?string + { + $value = trim((string) $value); + + if ($value === '') { + return null; + } + + $countryCode = self::countryCodeFromLabelOrCode($value); + + if (! $countryCode) { + return $value; + } + + return self::labelFromCountryCode($countryCode) ?? $value; + } + + /** + * @return Collection + */ + private static function countries(): Collection + { + static $countries; + + if ($countries instanceof Collection) { + return $countries; + } + + $countries = collect(CountriesEnum::cases()) + ->map(function (CountriesEnum $country): array { + $countryKey = $country->value; + $iso2 = strtoupper(explode('_', $countryKey)[0] ?? $countryKey); + $label = (string) trans("filament-country-code-field::countries.{$countryKey}"); + $englishLabel = (string) trans("filament-country-code-field::countries.{$countryKey}", [], 'en'); + + return [ + 'country_code' => $country->getCountryCode(), + 'iso2' => $iso2, + 'label' => $label, + 'english_label' => $englishLabel, + ]; + }) + ->values(); + + return $countries; + } + + private static function normalizeLabel(string $value): string + { + $value = mb_strtolower(trim($value)); + $value = preg_replace('/[^a-z0-9]+/u', ' ', $value) ?? $value; + + return trim(preg_replace('/\s+/', ' ', $value) ?? $value); + } +} diff --git a/composer.json b/composer.json index d13cde7ed..f71037373 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "require": { "php": "^8.2", "a909m/filament-statefusion": "^2.3", + "ariaieboy/filament-currency": "^3.0", "bezhansalleh/filament-language-switch": "^4.1", "cheesegrits/filament-google-maps": "^5.0", "dutchcodingcompany/filament-developer-logins": "^2.1", @@ -23,9 +24,11 @@ "nwidart/laravel-modules": "^11.0", "pxlrbt/filament-activity-log": "^2.1", "socialiteproviders/apple": "^5.9", - "spatie/laravel-permission": "^7.2", + "spatie/laravel-permission": "^6.24", "spatie/laravel-settings": "^3.7", "stechstudio/filament-impersonate": "^5.1", + "tallcms/cms": "^3.2", + "tapp/filament-country-code-field": "^2.0", "ysfkaya/filament-phone-input": "^4.1" }, "require-dev": { diff --git a/config/money.php b/config/money.php new file mode 100644 index 000000000..9663016f2 --- /dev/null +++ b/config/money.php @@ -0,0 +1,1843 @@ + [ + + 'currency' => env('MONEY_DEFAULTS_CURRENCY', 'USD'), + + 'convert' => env('MONEY_DEFAULTS_CONVERT', false), + + ], + + 'currencies' => [ + + 'AED' => [ + 'name' => 'UAE Dirham', + 'code' => 784, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'د.إ', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'AFN' => [ + 'name' => 'Afghani', + 'code' => 971, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '؋', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'ALL' => [ + 'name' => 'Lek', + 'code' => 8, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'L', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'AMD' => [ + 'name' => 'Armenian Dram', + 'code' => 51, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'դր.', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'ANG' => [ + 'name' => 'Netherlands Antillean Guilder', + 'code' => 532, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'ƒ', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'AOA' => [ + 'name' => 'Kwanza', + 'code' => 973, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Kz', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'ARS' => [ + 'name' => 'Argentine Peso', + 'code' => 32, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'AUD' => [ + 'name' => 'Australian Dollar', + 'code' => 36, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ' ', + ], + + 'AWG' => [ + 'name' => 'Aruban Florin', + 'code' => 533, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'ƒ', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'AZN' => [ + 'name' => 'Azerbaijanian Manat', + 'code' => 944, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₼', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BAM' => [ + 'name' => 'Convertible Mark', + 'code' => 977, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'КМ', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BBD' => [ + 'name' => 'Barbados Dollar', + 'code' => 52, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BDT' => [ + 'name' => 'Taka', + 'code' => 50, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '৳', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BGN' => [ + 'name' => 'Bulgarian Lev', + 'code' => 975, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'лв', + 'symbol_first' => false, + 'decimal_mark' => ',', + 'thousands_separator' => ' ', + ], + + 'BHD' => [ + 'name' => 'Bahraini Dinar', + 'code' => 48, + 'precision' => 3, + 'subunit' => 1000, + 'symbol' => 'ب.د', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BIF' => [ + 'name' => 'Burundi Franc', + 'code' => 108, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'Fr', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BMD' => [ + 'name' => 'Bermudian Dollar', + 'code' => 60, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BND' => [ + 'name' => 'Brunei Dollar', + 'code' => 96, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BOB' => [ + 'name' => 'Boliviano', + 'code' => 68, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Bs.', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BOV' => [ + 'name' => 'Mvdol', + 'code' => 984, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Bs.', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BRL' => [ + 'name' => 'Brazilian Real', + 'code' => 986, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'R$', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'BSD' => [ + 'name' => 'Bahamian Dollar', + 'code' => 44, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BTN' => [ + 'name' => 'Ngultrum', + 'code' => 64, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Nu.', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BWP' => [ + 'name' => 'Pula', + 'code' => 72, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'P', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'BYN' => [ + 'name' => 'Belarussian Ruble', + 'code' => 974, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'Br', + 'symbol_first' => false, + 'decimal_mark' => ',', + 'thousands_separator' => ' ', + ], + + 'BZD' => [ + 'name' => 'Belize Dollar', + 'code' => 84, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'CAD' => [ + 'name' => 'Canadian Dollar', + 'code' => 124, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'CDF' => [ + 'name' => 'Congolese Franc', + 'code' => 976, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Fr', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'CHF' => [ + 'name' => 'Swiss Franc', + 'code' => 756, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'CHF', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'CLF' => [ + 'name' => 'Unidades de fomento', + 'code' => 990, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'UF', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'CLP' => [ + 'name' => 'Chilean Peso', + 'code' => 152, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'CNY' => [ + 'name' => 'Yuan Renminbi', + 'code' => 156, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '¥', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'COP' => [ + 'name' => 'Colombian Peso', + 'code' => 170, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'CRC' => [ + 'name' => 'Costa Rican Colon', + 'code' => 188, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₡', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'CUC' => [ + 'name' => 'Peso Convertible', + 'code' => 931, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'CUP' => [ + 'name' => 'Cuban Peso', + 'code' => 192, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'CVE' => [ + 'name' => 'Cape Verde Escudo', + 'code' => 132, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'CZK' => [ + 'name' => 'Czech Koruna', + 'code' => 203, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Kč', + 'symbol_first' => false, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'DJF' => [ + 'name' => 'Djibouti Franc', + 'code' => 262, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'Fdj', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'DKK' => [ + 'name' => 'Danish Krone', + 'code' => 208, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'kr', + 'symbol_first' => false, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'DOP' => [ + 'name' => 'Dominican Peso', + 'code' => 214, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'DZD' => [ + 'name' => 'Algerian Dinar', + 'code' => 12, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'د.ج', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'EGP' => [ + 'name' => 'Egyptian Pound', + 'code' => 818, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'ج.م', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'ERN' => [ + 'name' => 'Nakfa', + 'code' => 232, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Nfk', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'ETB' => [ + 'name' => 'Ethiopian Birr', + 'code' => 230, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Br', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'EUR' => [ + 'name' => 'Euro', + 'code' => 978, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '€', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'FJD' => [ + 'name' => 'Fiji Dollar', + 'code' => 242, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'FKP' => [ + 'name' => 'Falkland Islands Pound', + 'code' => 238, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '£', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'GBP' => [ + 'name' => 'Pound Sterling', + 'code' => 826, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '£', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'GEL' => [ + 'name' => 'Lari', + 'code' => 981, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₾', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'GHS' => [ + 'name' => 'Ghana Cedi', + 'code' => 936, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₵', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'GIP' => [ + 'name' => 'Gibraltar Pound', + 'code' => 292, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '£', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'GMD' => [ + 'name' => 'Dalasi', + 'code' => 270, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'D', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'GNF' => [ + 'name' => 'Guinea Franc', + 'code' => 324, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'Fr', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'GTQ' => [ + 'name' => 'Quetzal', + 'code' => 320, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Q', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'GYD' => [ + 'name' => 'Guyana Dollar', + 'code' => 328, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'HKD' => [ + 'name' => 'Hong Kong Dollar', + 'code' => 344, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'HNL' => [ + 'name' => 'Lempira', + 'code' => 340, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'L', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'HRK' => [ + 'name' => 'Croatian Kuna', + 'code' => 191, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'kn', + 'symbol_first' => false, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'HTG' => [ + 'name' => 'Gourde', + 'code' => 332, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'G', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'HUF' => [ + 'name' => 'Forint', + 'code' => 348, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Ft', + 'symbol_first' => false, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'IDR' => [ + 'name' => 'Rupiah', + 'code' => 360, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Rp', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'ILS' => [ + 'name' => 'New Israeli Sheqel', + 'code' => 376, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₪', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'INR' => [ + 'name' => 'Indian Rupee', + 'code' => 356, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₹', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'IQD' => [ + 'name' => 'Iraqi Dinar', + 'code' => 368, + 'precision' => 3, + 'subunit' => 1000, + 'symbol' => 'ع.د', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'IRR' => [ + 'name' => 'Iranian Rial', + 'code' => 364, + 'precision' => 0, + 'subunit' => 100, + 'symbol' => '﷼', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'ISK' => [ + 'name' => 'Iceland Krona', + 'code' => 352, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'kr', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'JMD' => [ + 'name' => 'Jamaican Dollar', + 'code' => 388, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'JOD' => [ + 'name' => 'Jordanian Dinar', + 'code' => 400, + 'precision' => 3, + 'subunit' => 100, + 'symbol' => 'د.ا', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'JPY' => [ + 'name' => 'Yen', + 'code' => 392, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => '¥', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'KES' => [ + 'name' => 'Kenyan Shilling', + 'code' => 404, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'KSh', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'KGS' => [ + 'name' => 'Som', + 'code' => 417, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'som', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'KHR' => [ + 'name' => 'Riel', + 'code' => 116, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '៛', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'KMF' => [ + 'name' => 'Comoro Franc', + 'code' => 174, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'Fr', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'KPW' => [ + 'name' => 'North Korean Won', + 'code' => 408, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₩', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'KRW' => [ + 'name' => 'Won', + 'code' => 410, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => '₩', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'KWD' => [ + 'name' => 'Kuwaiti Dinar', + 'code' => 414, + 'precision' => 3, + 'subunit' => 1000, + 'symbol' => 'د.ك', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'KYD' => [ + 'name' => 'Cayman Islands Dollar', + 'code' => 136, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'KZT' => [ + 'name' => 'Tenge', + 'code' => 398, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '〒', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'LAK' => [ + 'name' => 'Kip', + 'code' => 418, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₭', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'LBP' => [ + 'name' => 'Lebanese Pound', + 'code' => 422, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'ل.ل', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'LKR' => [ + 'name' => 'Sri Lanka Rupee', + 'code' => 144, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₨', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'LRD' => [ + 'name' => 'Liberian Dollar', + 'code' => 430, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'LSL' => [ + 'name' => 'Loti', + 'code' => 426, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'L', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'LTL' => [ + 'name' => 'Lithuanian Litas', + 'code' => 440, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Lt', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'LVL' => [ + 'name' => 'Latvian Lats', + 'code' => 428, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Ls', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'LYD' => [ + 'name' => 'Libyan Dinar', + 'code' => 434, + 'precision' => 3, + 'subunit' => 1000, + 'symbol' => 'ل.د', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MAD' => [ + 'name' => 'Moroccan Dirham', + 'code' => 504, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'د.م.', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MDL' => [ + 'name' => 'Moldovan Leu', + 'code' => 498, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'L', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MGA' => [ + 'name' => 'Malagasy Ariary', + 'code' => 969, + 'precision' => 2, + 'subunit' => 5, + 'symbol' => 'Ar', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MKD' => [ + 'name' => 'Denar', + 'code' => 807, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'ден', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MMK' => [ + 'name' => 'Kyat', + 'code' => 104, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'K', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MNT' => [ + 'name' => 'Tugrik', + 'code' => 496, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₮', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MOP' => [ + 'name' => 'Pataca', + 'code' => 446, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'P', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MRO' => [ + 'name' => 'Ouguiya', + 'code' => 478, + 'precision' => 2, + 'subunit' => 5, + 'symbol' => 'UM', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MUR' => [ + 'name' => 'Mauritius Rupee', + 'code' => 480, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₨', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MVR' => [ + 'name' => 'Rufiyaa', + 'code' => 462, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'MVR', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MWK' => [ + 'name' => 'Kwacha', + 'code' => 454, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'MK', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MXN' => [ + 'name' => 'Mexican Peso', + 'code' => 484, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MYR' => [ + 'name' => 'Malaysian Ringgit', + 'code' => 458, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'RM', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'MZN' => [ + 'name' => 'Mozambique Metical', + 'code' => 943, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'MTn', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'NAD' => [ + 'name' => 'Namibia Dollar', + 'code' => 516, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'NGN' => [ + 'name' => 'Naira', + 'code' => 566, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₦', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'NIO' => [ + 'name' => 'Cordoba Oro', + 'code' => 558, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'C$', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'NOK' => [ + 'name' => 'Norwegian Krone', + 'code' => 578, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'kr', + 'symbol_first' => false, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'NPR' => [ + 'name' => 'Nepalese Rupee', + 'code' => 524, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₨', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'NZD' => [ + 'name' => 'New Zealand Dollar', + 'code' => 554, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'OMR' => [ + 'name' => 'Rial Omani', + 'code' => 512, + 'precision' => 3, + 'subunit' => 1000, + 'symbol' => 'ر.ع.', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'PAB' => [ + 'name' => 'Balboa', + 'code' => 590, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'B/.', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'PEN' => [ + 'name' => 'Sol', + 'code' => 604, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'S/', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'PGK' => [ + 'name' => 'Kina', + 'code' => 598, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'K', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'PHP' => [ + 'name' => 'Philippine Peso', + 'code' => 608, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₱', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'PKR' => [ + 'name' => 'Pakistan Rupee', + 'code' => 586, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₨', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'PLN' => [ + 'name' => 'Zloty', + 'code' => 985, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'zł', + 'symbol_first' => false, + 'decimal_mark' => ',', + 'thousands_separator' => ' ', + ], + + 'PYG' => [ + 'name' => 'Guarani', + 'code' => 600, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => '₲', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'QAR' => [ + 'name' => 'Qatari Rial', + 'code' => 634, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'ر.ق', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'RON' => [ + 'name' => 'New Romanian Leu', + 'code' => 946, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Lei', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'RSD' => [ + 'name' => 'Serbian Dinar', + 'code' => 941, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'РСД', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'RUB' => [ + 'name' => 'Russian Ruble', + 'code' => 643, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₽', + 'symbol_first' => false, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'RWF' => [ + 'name' => 'Rwanda Franc', + 'code' => 646, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'FRw', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SAR' => [ + 'name' => 'Saudi Riyal', + 'code' => 682, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'ر.س', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SBD' => [ + 'name' => 'Solomon Islands Dollar', + 'code' => 90, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SCR' => [ + 'name' => 'Seychelles Rupee', + 'code' => 690, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₨', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SDG' => [ + 'name' => 'Sudanese Pound', + 'code' => 938, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '£', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SEK' => [ + 'name' => 'Swedish Krona', + 'code' => 752, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'kr', + 'symbol_first' => false, + 'decimal_mark' => ',', + 'thousands_separator' => ' ', + ], + + 'SGD' => [ + 'name' => 'Singapore Dollar', + 'code' => 702, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SHP' => [ + 'name' => 'Saint Helena Pound', + 'code' => 654, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '£', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SLL' => [ + 'name' => 'Leone', + 'code' => 694, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Le', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SOS' => [ + 'name' => 'Somali Shilling', + 'code' => 706, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Sh', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SRD' => [ + 'name' => 'Surinam Dollar', + 'code' => 968, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SSP' => [ + 'name' => 'South Sudanese Pound', + 'code' => 728, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '£', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'STD' => [ + 'name' => 'Dobra', + 'code' => 678, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Db', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SVC' => [ + 'name' => 'El Salvador Colon', + 'code' => 222, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₡', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SYP' => [ + 'name' => 'Syrian Pound', + 'code' => 760, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '£S', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'SZL' => [ + 'name' => 'Lilangeni', + 'code' => 748, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'E', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'THB' => [ + 'name' => 'Baht', + 'code' => 764, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '฿', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'TJS' => [ + 'name' => 'Somoni', + 'code' => 972, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'ЅМ', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'TMT' => [ + 'name' => 'Turkmenistan New Manat', + 'code' => 934, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'T', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'TND' => [ + 'name' => 'Tunisian Dinar', + 'code' => 788, + 'precision' => 3, + 'subunit' => 1000, + 'symbol' => 'د.ت', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'TOP' => [ + 'name' => 'Pa’anga', + 'code' => 776, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'T$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'TRY' => [ + 'name' => 'Turkish Lira', + 'code' => 949, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₺', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'TTD' => [ + 'name' => 'Trinidad and Tobago Dollar', + 'code' => 780, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'TWD' => [ + 'name' => 'New Taiwan Dollar', + 'code' => 901, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'TZS' => [ + 'name' => 'Tanzanian Shilling', + 'code' => 834, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Sh', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'UAH' => [ + 'name' => 'Hryvnia', + 'code' => 980, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '₴', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'UGX' => [ + 'name' => 'Uganda Shilling', + 'code' => 800, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'USh', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'USD' => [ + 'name' => 'US Dollar', + 'code' => 840, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'UYU' => [ + 'name' => 'Peso Uruguayo', + 'code' => 858, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'UZS' => [ + 'name' => 'Uzbekistan Sum', + 'code' => 860, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'лв', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + // This currency is no longer used. It will be removed soon. + 'VEF' => [ + 'name' => 'Bolivar', + 'code' => 937, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Bs F', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'VES' => [ + 'name' => 'Bolívar Soberano', + 'code' => 928, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Bs S', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'VED' => [ + 'name' => 'Dijital Bolívar', + 'code' => 926, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'Bs D', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'VND' => [ + 'name' => 'Dong', + 'code' => 704, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => '₫', + 'symbol_first' => true, + 'decimal_mark' => ',', + 'thousands_separator' => '.', + ], + + 'VUV' => [ + 'name' => 'Vatu', + 'code' => 548, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'Vt', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'WST' => [ + 'name' => 'Tala', + 'code' => 882, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'T', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'XAF' => [ + 'name' => 'CFA Franc BEAC', + 'code' => 950, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'Fr', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'XAG' => [ + 'name' => 'Silver', + 'code' => 961, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'oz t', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'XAU' => [ + 'name' => 'Gold', + 'code' => 959, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'oz t', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'XCD' => [ + 'name' => 'East Caribbean Dollar', + 'code' => 951, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'XDR' => [ + 'name' => 'SDR (Special Drawing Right)', + 'code' => 960, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'SDR', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'XOF' => [ + 'name' => 'CFA Franc BCEAO', + 'code' => 952, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'Fr', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'XPF' => [ + 'name' => 'CFP Franc', + 'code' => 953, + 'precision' => 0, + 'subunit' => 1, + 'symbol' => 'Fr', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'YER' => [ + 'name' => 'Yemeni Rial', + 'code' => 886, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '﷼', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'ZAR' => [ + 'name' => 'Rand', + 'code' => 710, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'R', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'ZMW' => [ + 'name' => 'Zambian Kwacha', + 'code' => 967, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => 'ZK', + 'symbol_first' => false, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + + 'ZWL' => [ + 'name' => 'Zimbabwe Dollar', + 'code' => 932, + 'precision' => 2, + 'subunit' => 100, + 'symbol' => '$', + 'symbol_first' => true, + 'decimal_mark' => '.', + 'thousands_separator' => ',', + ], + ], + +]; diff --git a/config/tallcms.php b/config/tallcms.php new file mode 100644 index 000000000..0f45d0b70 --- /dev/null +++ b/config/tallcms.php @@ -0,0 +1,569 @@ + (function () { + $composerJson = dirname(__DIR__).'/composer.json'; + if (file_exists($composerJson)) { + $data = json_decode(file_get_contents($composerJson), true); + + return $data['version'] ?? 'unknown'; + } + + return 'unknown'; + })(), + + /* + |-------------------------------------------------------------------------- + | Operation Mode + |-------------------------------------------------------------------------- + | + | Determines how TallCMS operates. Auto-detection works in most cases: + | - 'standalone': Full TallCMS installation (tallcms/tallcms skeleton) + | - 'plugin': Installed as a plugin in existing Filament app + | - null: Auto-detect based on .tallcms-standalone marker file + | + */ + 'mode' => env('TALLCMS_MODE'), + + /* + |-------------------------------------------------------------------------- + | Database Configuration + |-------------------------------------------------------------------------- + | + | Table prefix for all TallCMS tables. Default 'tallcms_' maintains + | compatibility with v1.x installations. Can be customized in plugin + | mode to avoid conflicts with existing tables. + | + */ + 'database' => [ + 'prefix' => env('TALLCMS_TABLE_PREFIX', 'tallcms_'), + ], + + /* + |-------------------------------------------------------------------------- + | Plugin Mode Settings + |-------------------------------------------------------------------------- + | + | Configuration specific to plugin mode operation. These settings are + | ignored in standalone mode. + | + */ + 'plugin_mode' => [ + // Enable frontend CMS page routes. + // When enabled, TallCMS registers both / (homepage) and /{slug} routes. + // WARNING: Without a prefix, this will override your app's homepage route! + 'routes_enabled' => env('TALLCMS_ROUTES_ENABLED', false), + + // Optional URL prefix for CMS routes (e.g., 'cms' results in /cms and /cms/{slug}) + // Leave empty for root-level routes (/, /about, /contact) + // When empty, smart exclusions prevent conflicts with your app routes. + 'routes_prefix' => env('TALLCMS_ROUTES_PREFIX', ''), + + // Route name prefix for plugin mode (e.g., 'tallcms.' results in tallcms.cms.page) + 'route_name_prefix' => env('TALLCMS_PLUGIN_ROUTE_NAME_PREFIX', 'tallcms.'), + + // Route exclusion pattern - paths matching this regex are excluded from CMS routing. + // Default excludes common Laravel/Filament paths. Panel path is auto-excluded. + // + // In NON-i18n mode with standard format (^(?!foo|bar).*$): Merged with base exclusions. + // In NON-i18n mode with custom regex: Used as-is, replaces default pattern entirely. + // NOTE: When using custom regex, 'additional_exclusions' is ignored. + // In i18n mode: Only standard negative lookahead format is merged; other formats ignored. + 'route_exclusions' => env('TALLCMS_PLUGIN_ROUTE_EXCLUSIONS', + env('TALLCMS_ROUTE_EXCLUSIONS', // backward compat + '^(?!admin|app|api|livewire|sanctum|storage|build|vendor|health|_).*$' + ) + ), + + // Additional route exclusions as pipe-separated list (e.g., 'dashboard|settings|profile'). + // Merged with base exclusions when using standard route_exclusions format. + // NOTE: Ignored when route_exclusions is set to a non-standard custom regex. + // Recommended for i18n mode where custom regex is not supported. + 'additional_exclusions' => env('TALLCMS_ADDITIONAL_EXCLUSIONS', ''), + + // Enable preview routes (/preview/page/{id}, /preview/post/{id}) + 'preview_routes_enabled' => env('TALLCMS_PREVIEW_ROUTES_ENABLED', true), + + // Enable API routes (/api/contact) + 'api_routes_enabled' => env('TALLCMS_API_ROUTES_ENABLED', true), + + // Optional prefix for essential routes (preview, contact API) to avoid conflicts + // e.g., 'tallcms' results in /tallcms/preview/page/{id} + 'essential_routes_prefix' => env('TALLCMS_ESSENTIAL_ROUTES_PREFIX', ''), + + // Enable core SEO routes (sitemap.xml, robots.txt). + // These are always registered at root level (no prefix) since search + // engines expect them at standard locations. Safe to enable. + 'seo_routes_enabled' => env('TALLCMS_SEO_ROUTES_ENABLED', true), + + // Enable archive routes (RSS feed, category archives, author archives). + // These routes (/feed, /category/{slug}, /author/{slug}) may conflict + // with your app's routes. Disabled by default in plugin mode. + 'archive_routes_enabled' => env('TALLCMS_ARCHIVE_ROUTES_ENABLED', false), + + // Optional prefix for archive routes to avoid conflicts. + // e.g., 'blog' results in /blog/feed, /blog/category/{slug}, /blog/author/{slug} + 'archive_routes_prefix' => env('TALLCMS_ARCHIVE_ROUTES_PREFIX', ''), + + // Enable the TallCMS plugin system. + // When enabled, the Plugin Manager page is visible and third-party plugins can be loaded. + 'plugins_enabled' => env('TALLCMS_PLUGINS_ENABLED', true), + + // Enable the TallCMS theme system. + // When enabled, the Theme Manager page is visible and themes can be loaded. + 'themes_enabled' => env('TALLCMS_THEMES_ENABLED', true), + + // User model class. Must implement TallCmsUserContract. + // Default works with standard Laravel User model with HasRoles trait. + 'user_model' => env('TALLCMS_USER_MODEL', 'App\\Models\\User'), + + // Skip installer.lock check for maintenance mode in plugin mode. + // In plugin mode, the host app doesn't use TallCMS's installer, + // so we assume the app is properly installed. Default: true + 'skip_installer_check' => env('TALLCMS_SKIP_INSTALLER_CHECK', true), + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Configuration + |-------------------------------------------------------------------------- + | + | Configuration for authentication guards used by TallCMS roles and + | permissions. This should match your Filament panel's guard. + | + */ + 'auth' => [ + // Guard name for roles and permissions (should match Filament panel guard) + 'guard' => env('TALLCMS_AUTH_GUARD', 'web'), + + // Login route for preview authentication redirect + // Can be a route name (e.g., 'filament.admin.auth.login') or URL + // Leave null to auto-detect Filament's login route + 'login_route' => env('TALLCMS_LOGIN_ROUTE'), + ], + + /* + |-------------------------------------------------------------------------- + | Filament Panel Configuration + |-------------------------------------------------------------------------- + | + | These settings are dynamically set by TallCmsPlugin when registered. + | They allow customization of navigation group and sort order. + | + */ + 'filament' => [ + // Panel ID for route generation in notifications + // Used for constructing admin panel URLs like filament.{panel_id}.resources.* + 'panel_id' => env('TALLCMS_PANEL_ID', 'admin'), + + // Panel path for URL construction and middleware exclusions + 'panel_path' => env('TALLCMS_PANEL_PATH', 'admin'), + + // Navigation group override - when set, CMS resources/pages use this group. + // Note: UserResource stays in 'User Management' regardless of this setting. + // Leave unset (null) to use per-resource defaults (Content Management, Settings, etc.) + 'navigation_group' => env('TALLCMS_NAVIGATION_GROUP'), + + // Navigation sort override - when set, CMS resources/pages use this sort. + // Leave unset (null) to use per-resource defaults. + 'navigation_sort' => env('TALLCMS_NAVIGATION_SORT') !== null + ? (int) env('TALLCMS_NAVIGATION_SORT') + : null, + ], + + /* + |-------------------------------------------------------------------------- + | Contact Information + |-------------------------------------------------------------------------- + | + | Default contact information used in templates and merge tags. + | + */ + 'contact_email' => env('TALLCMS_CONTACT_EMAIL'), + 'company_name' => env('TALLCMS_COMPANY_NAME'), + 'company_address' => env('TALLCMS_COMPANY_ADDRESS'), + + /* + |-------------------------------------------------------------------------- + | Publishing Workflow + |-------------------------------------------------------------------------- + | + | Configuration for the content publishing workflow including + | revision history and preview tokens. + | + */ + 'publishing' => [ + // Maximum number of automatic revisions to keep per content item. + // Set to null for unlimited. Default: 100 + 'revision_limit' => env('CMS_REVISION_LIMIT', 100), + + // Maximum number of manual (pinned) snapshots to keep per content item. + // Set to null for unlimited. Default: 50 + 'revision_manual_limit' => env('CMS_REVISION_MANUAL_LIMIT', 50), + + // Notification channels for workflow events + // Available: 'mail', 'database' + 'notification_channels' => explode(',', env('CMS_NOTIFICATION_CHANNELS', 'mail,database')), + + // Default preview token expiry in hours + 'default_preview_expiry_hours' => 24, + ], + + /* + |-------------------------------------------------------------------------- + | Plugin System + |-------------------------------------------------------------------------- + | + | Configuration for the TallCMS plugin system including license management. + | The Plugin Manager UI is always available, but local plugin loading + | requires explicit opt-in via plugin_mode.plugins_enabled. + | + */ + 'plugins' => [ + // Path where plugins are stored + 'path' => env('TALLCMS_PLUGINS_PATH', base_path('plugins')), + + // Allow ZIP-based plugin uploads through admin UI + 'allow_uploads' => env('TALLCMS_PLUGIN_ALLOW_UPLOADS', env('PLUGIN_ALLOW_UPLOADS', true)), + + // Maximum upload size for plugin ZIP files (bytes). Default: 50MB + 'max_upload_size' => env('TALLCMS_PLUGIN_MAX_UPLOAD_SIZE', env('PLUGIN_MAX_UPLOAD_SIZE', 50 * 1024 * 1024)), + + // Plugin discovery caching + 'cache_enabled' => env('TALLCMS_PLUGIN_CACHE_ENABLED', env('PLUGIN_CACHE_ENABLED', true)), + 'cache_ttl' => 3600, // 1 hour + + // Automatically run plugin migrations on install + 'auto_migrate' => env('TALLCMS_PLUGIN_AUTO_MIGRATE', env('PLUGIN_AUTO_MIGRATE', true)), + + // License management settings + 'license' => [ + // License proxy URL for official TallCMS plugins + 'proxy_url' => env('TALLCMS_LICENSE_PROXY_URL', 'https://tallcms.com'), + + // Cache TTL for license validation results (seconds). Default: 6 hours + 'cache_ttl' => 21600, + + // Grace period when license server unreachable (days). Default: 7 + 'offline_grace_days' => 7, + + // Grace period after license expiration (days). Default: 14 + 'renewal_grace_days' => 14, + + // How often to check for updates (seconds). Default: 24 hours + 'update_check_interval' => 86400, + + // Purchase URLs for plugins (shown when no license is active) + 'purchase_urls' => [ + 'tallcms/pro' => 'https://checkout.anystack.sh/tallcms-pro-plugin', + 'tallcms/mega-menu' => 'https://checkout.anystack.sh/tallcms-mega-menu-plugin', + ], + + // Download URLs for plugins (shown when license is valid) + 'download_urls' => [ + 'tallcms/pro' => 'https://anystack.sh/download/tallcms-pro-plugin', + 'tallcms/mega-menu' => 'https://anystack.sh/download/tallcms-mega-menu-plugin', + ], + ], + + // Official plugin catalog (shown in Plugin Manager) + 'catalog' => [ + 'tallcms/pro' => [ + 'name' => 'TallCMS Pro', + 'slug' => 'pro', + 'vendor' => 'tallcms', + 'description' => 'Advanced blocks, analytics, and integrations for TallCMS.', + 'author' => 'TallCMS', + 'homepage' => 'https://tallcms.com/pro', + 'icon' => 'heroicon-o-sparkles', + 'category' => 'official', + 'featured' => true, + 'download_url' => 'https://anystack.sh/download/tallcms-pro-plugin', + 'purchase_url' => 'https://checkout.anystack.sh/tallcms-pro-plugin', + ], + 'tallcms/mega-menu' => [ + 'name' => 'TallCMS Mega Menu', + 'slug' => 'mega-menu', + 'vendor' => 'tallcms', + 'description' => 'Create stunning mega menus for your website with ease. Build rich, multi-column dropdown menus with images, icons, and custom layouts.', + 'author' => 'TallCMS', + 'homepage' => 'https://tallcms.com/mega-menu', + 'icon' => 'heroicon-o-bars-3-bottom-left', + 'category' => 'official', + 'featured' => true, + 'download_url' => 'https://anystack.sh/download/tallcms-mega-menu-plugin', + 'purchase_url' => 'https://checkout.anystack.sh/tallcms-mega-menu-plugin', + ], + ], + ], + + /* + |-------------------------------------------------------------------------- + | Theme System + |-------------------------------------------------------------------------- + | + | Configuration for the TallCMS theme system. The Theme Manager UI is + | always available, but theme loading requires explicit opt-in via + | plugin_mode.themes_enabled in plugin mode. + | + */ + 'themes' => [ + // Path where themes are stored + 'path' => env('TALLCMS_THEMES_PATH', base_path('themes')), + + // Allow ZIP-based theme uploads through admin UI + 'allow_uploads' => env('TALLCMS_THEME_ALLOW_UPLOADS', true), + + // Maximum upload size for theme ZIP files (bytes). Default: 100MB + 'max_upload_size' => env('TALLCMS_THEME_MAX_UPLOAD_SIZE', 100 * 1024 * 1024), + + // Theme discovery caching + 'cache_enabled' => env('TALLCMS_THEME_CACHE_ENABLED', false), + 'cache_ttl' => 3600, // 1 hour + + // Preview session duration (minutes) + 'preview_duration' => 30, + + // Rollback availability window (hours) + 'rollback_duration' => 24, + ], + + /* + |-------------------------------------------------------------------------- + | REST API + |-------------------------------------------------------------------------- + | + | Configuration for the TallCMS REST API. The API provides full CRUD + | operations for Pages, Posts, Categories, and Media with authentication + | via Laravel Sanctum tokens. + | + */ + 'api' => [ + // Enable or disable the REST API + 'enabled' => env('TALLCMS_API_ENABLED', false), + + // API route prefix (e.g., 'api/v1/tallcms' results in /api/v1/tallcms/pages) + 'prefix' => env('TALLCMS_API_PREFIX', 'api/v1/tallcms'), + + // Standard rate limit (requests per minute) + 'rate_limit' => env('TALLCMS_API_RATE_LIMIT', 60), + + // Authentication rate limit (failed attempts before lockout) + 'auth_rate_limit' => env('TALLCMS_API_AUTH_RATE_LIMIT', 5), + + // Authentication lockout duration (minutes) + 'auth_lockout_minutes' => env('TALLCMS_API_AUTH_LOCKOUT', 15), + + // Default token expiry (days) + 'token_expiry_days' => env('TALLCMS_API_TOKEN_EXPIRY', 365), + + // Maximum items per page for pagination + 'max_per_page' => 100, + ], + + /* + |-------------------------------------------------------------------------- + | Webhooks + |-------------------------------------------------------------------------- + | + | Configuration for webhook delivery to external services. Webhooks notify + | external systems when content is created, updated, published, or deleted. + | + */ + 'webhooks' => [ + // Enable or disable webhooks + 'enabled' => env('TALLCMS_WEBHOOKS_ENABLED', false), + + // Request timeout (seconds) + 'timeout' => env('TALLCMS_WEBHOOK_TIMEOUT', 30), + + // Maximum retry attempts + 'max_retries' => env('TALLCMS_WEBHOOK_MAX_RETRIES', 3), + + // Delay before retry attempts (seconds) - retry 1, 2, 3 + 'retry_backoff' => [60, 300, 900], + + // Maximum response body size to store (bytes) + 'response_max_size' => 10000, + + // Allowed hosts (empty = allow all public IPs) + 'allowed_hosts' => [], + + // Explicitly blocked hosts + 'blocked_hosts' => [], + + // Queue name for webhook jobs + 'queue' => env('TALLCMS_WEBHOOK_QUEUE', 'default'), + ], + + /* + |-------------------------------------------------------------------------- + | Internationalization (i18n) + |-------------------------------------------------------------------------- + | + | Core i18n configuration. Locales are merged from multiple sources: + | - Config: Base locales (always available) + | - Plugins: Can ADD new locale codes (cannot override config) + | - DB: Can MODIFY existing locales (enable/disable/rename, cannot add) + | + */ + 'i18n' => [ + // Master switch for multilingual features + 'enabled' => env('TALLCMS_I18N_ENABLED', false), + + // Base locales (always available, plugins can add new ones, DB can modify existing) + 'locales' => [ + 'en' => [ + 'label' => 'English', + 'native' => 'English', + 'rtl' => false, + ], + 'zh_CN' => [ + 'label' => 'Chinese (Simplified)', + 'native' => '简体中文', + 'rtl' => false, + ], + ], + + // Default/fallback locale (must exist in registry) + 'default_locale' => env('TALLCMS_DEFAULT_LOCALE', 'en'), + + // URL strategy: 'prefix' (/en/about) or 'none' (query param fallback) + 'url_strategy' => 'prefix', + + // Hide default locale from URL (/ instead of /en/) + 'hide_default_locale' => env('TALLCMS_HIDE_DEFAULT_LOCALE', true), + + // Fallback when translation missing: 'default', 'empty', 'key' + 'fallback_behavior' => 'default', + + // Remember locale preference in session + 'remember_locale' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Comments + |-------------------------------------------------------------------------- + | + | Configuration for the blog post commenting system. Comments require + | admin approval before appearing publicly. + | + */ + 'comments' => [ + 'enabled' => env('TALLCMS_COMMENTS_ENABLED', true), + 'moderation' => env('TALLCMS_COMMENTS_MODERATION', 'manual'), // 'manual' = require approval, 'auto' = publish immediately + 'max_depth' => 2, // top-level + 1 reply level (min 1) + 'max_length' => 5000, // max comment content length + 'rate_limit' => 5, // max comments per IP per window + 'rate_limit_decay' => 600, // rate limit window in seconds + 'notification_channels' => ['mail', 'database'], + 'notify_on_approval' => true, // email commenter when approved + 'guest_comments' => true, // allow non-authenticated comments + ], + + /* + |-------------------------------------------------------------------------- + | Media Library + |-------------------------------------------------------------------------- + | + | Configuration for media library features including image optimization, + | variant generation, and responsive image handling. + | + */ + 'media' => [ + 'optimization' => [ + // Enable or disable automatic image optimization + 'enabled' => env('TALLCMS_MEDIA_OPTIMIZATION', true), + + // Queue name for optimization jobs + 'queue' => env('TALLCMS_MEDIA_QUEUE', 'default'), + + // WebP quality (0-100) + 'quality' => env('TALLCMS_MEDIA_QUALITY', 80), + + // Variant presets - customize sizes as needed + 'variants' => [ + 'thumbnail' => ['width' => 300, 'height' => 300, 'fit' => 'crop'], + 'medium' => ['width' => 800, 'height' => 600, 'fit' => 'contain'], + 'large' => ['width' => 1200, 'height' => 800, 'fit' => 'contain'], + ], + ], + ], + + /* + |-------------------------------------------------------------------------- + | Full-Text Search + |-------------------------------------------------------------------------- + | + | Configuration for the full-text search functionality using Laravel Scout. + | Requires SCOUT_DRIVER=database in your .env file. + | + */ + 'search' => [ + // Enable or disable search functionality + 'enabled' => env('TALLCMS_SEARCH_ENABLED', true), + + // Minimum query length required before searching + 'min_query_length' => 2, + + // Number of results per page on the search results page + 'results_per_page' => 10, + + // Maximum results per model type to avoid memory issues + 'max_results_per_type' => 50, + + // Which content types to include in search + 'searchable_types' => ['pages', 'posts'], + ], + + /* + |-------------------------------------------------------------------------- + | System Updates (Standalone Mode Only) + |-------------------------------------------------------------------------- + | + | Configuration for the one-click update system. These settings are + | IGNORED in plugin mode - use Composer for updates instead. + | + */ + 'updates' => [ + // Enable or disable the update system (standalone mode only) + 'enabled' => env('TALLCMS_UPDATES_ENABLED', true), + + // How often to check for updates (seconds). Default: 24 hours + 'check_interval' => 86400, + + // Cache TTL for GitHub API responses (seconds). Default: 1 hour + 'cache_ttl' => 3600, + + // GitHub repository for updates + 'github_repo' => 'tallcms/tallcms', + + // Optional GitHub token for higher API rate limits + 'github_token' => env('TALLCMS_GITHUB_TOKEN'), + + // Number of backup sets to retain + 'backup_retention' => 3, + + // Automatically backup files before updating + 'auto_backup' => true, + + // Require database backup before update + 'require_db_backup' => true, + + // Maximum database size for automatic backup (bytes). Default: 100MB + 'db_backup_size_limit' => 100 * 1024 * 1024, + + // Ed25519 public key for release signature verification (hex-encoded) + 'public_key' => env('TALLCMS_UPDATE_PUBLIC_KEY', '6c41c964c60dd5341f7ba649dcda6e6de4b0b7afac2fbb9489527987907d35a9'), + ], +]; diff --git a/config/theme.php b/config/theme.php new file mode 100644 index 000000000..8022600bd --- /dev/null +++ b/config/theme.php @@ -0,0 +1,8 @@ + env('TALLCMS_THEME_ACTIVE', 'talldaisy'), + 'themes_path' => base_path('themes'), + 'cache_themes' => env('TALLCMS_THEME_CACHE', true), + 'auto_discover' => true, +]; diff --git a/database/settings/2026_03_03_170000_add_default_country_code_to_general_settings.php b/database/settings/2026_03_03_170000_add_default_country_code_to_general_settings.php new file mode 100644 index 000000000..998a54e5e --- /dev/null +++ b/database/settings/2026_03_03_170000_add_default_country_code_to_general_settings.php @@ -0,0 +1,11 @@ +migrator->add('general.default_country_code', '+90'); + } +}; diff --git a/public/themes/talldaisy b/public/themes/talldaisy new file mode 120000 index 000000000..b122dab5b --- /dev/null +++ b/public/themes/talldaisy @@ -0,0 +1 @@ +../../vendor/tallcms/cms/resources/themes/talldaisy/public \ No newline at end of file