mirror of
https://github.com/openclassify/openclassify.git
synced 2026-04-14 11:12:09 -05:00
beta
This commit is contained in:
parent
024a21306c
commit
c1500cd8e5
@ -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.'),
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
70
Modules/Category/Policies/CategoryPolicy.php
Normal file
70
Modules/Category/Policies/CategoryPolicy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Category\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use Modules\Category\Models\Category;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class CategoryPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,18 +1,18 @@
|
||||
<?php
|
||||
namespace Modules\Listing\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Modules\Listing\Models\Listing;
|
||||
use Modules\Listing\Support\ListingPanelHelper;
|
||||
|
||||
class ListingController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
public function index()
|
||||
{
|
||||
$listings = Listing::where('status', 'active')
|
||||
->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'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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');
|
||||
|
||||
70
Modules/Listing/Policies/ListingPolicy.php
Normal file
70
Modules/Listing/Policies/ListingPolicy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Listing\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use Modules\Listing\Models\Listing;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class ListingPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
55
Modules/Listing/Support/ListingPanelHelper.php
Normal file
55
Modules/Listing/Support/ListingPanelHelper.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Listing\Support;
|
||||
|
||||
use App\Settings\GeneralSettings;
|
||||
use Throwable;
|
||||
|
||||
class ListingPanelHelper
|
||||
{
|
||||
public static 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'];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Modules/Location/Policies/CountryPolicy.php
Normal file
70
Modules/Location/Policies/CountryPolicy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Location\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use Modules\Location\Models\Country;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class CountryPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
70
app/Policies/CmsCategoryPolicy.php
Normal file
70
app/Policies/CmsCategoryPolicy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use TallCms\Cms\Models\CmsCategory;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class CmsCategoryPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
70
app/Policies/CmsCommentPolicy.php
Normal file
70
app/Policies/CmsCommentPolicy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use TallCms\Cms\Models\CmsComment;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class CmsCommentPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
70
app/Policies/CmsPagePolicy.php
Normal file
70
app/Policies/CmsPagePolicy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use TallCms\Cms\Models\CmsPage;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class CmsPagePolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
70
app/Policies/CmsPostPolicy.php
Normal file
70
app/Policies/CmsPostPolicy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use TallCms\Cms\Models\CmsPost;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class CmsPostPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
70
app/Policies/MediaCollectionPolicy.php
Normal file
70
app/Policies/MediaCollectionPolicy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use TallCms\Cms\Models\MediaCollection;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class MediaCollectionPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
70
app/Policies/RolePolicy.php
Normal file
70
app/Policies/RolePolicy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class RolePolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
70
app/Policies/TallcmsContactSubmissionPolicy.php
Normal file
70
app/Policies/TallcmsContactSubmissionPolicy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use TallCms\Cms\Models\TallcmsContactSubmission;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class TallcmsContactSubmissionPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
70
app/Policies/TallcmsMediaPolicy.php
Normal file
70
app/Policies/TallcmsMediaPolicy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use TallCms\Cms\Models\TallcmsMedia;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class TallcmsMediaPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
70
app/Policies/TallcmsMenuPolicy.php
Normal file
70
app/Policies/TallcmsMenuPolicy.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use TallCms\Cms\Models\TallcmsMenu;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class TallcmsMenuPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
67
app/Policies/UserPolicy.php
Normal file
67
app/Policies/UserPolicy.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class UserPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->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');
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -14,6 +14,8 @@ class GeneralSettings extends Settings
|
||||
|
||||
public string $default_language;
|
||||
|
||||
public string $default_country_code;
|
||||
|
||||
public array $currencies;
|
||||
|
||||
public string $sender_email;
|
||||
|
||||
168
app/Support/CountryCodeManager.php
Normal file
168
app/Support/CountryCodeManager.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Tapp\FilamentCountryCodeField\Enums\CountriesEnum;
|
||||
|
||||
class CountryCodeManager
|
||||
{
|
||||
public static function defaultCountryCode(): string
|
||||
{
|
||||
return self::normalizeCountryCode(config('app.default_country_code', '+90'));
|
||||
}
|
||||
|
||||
public static function defaultCountryIso2(): string
|
||||
{
|
||||
return self::iso2FromCountryCode(self::defaultCountryCode()) ?? 'TR';
|
||||
}
|
||||
|
||||
public static function normalizeCountryCode(null | string $value): string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
|
||||
if ($value === '') {
|
||||
return '+90';
|
||||
}
|
||||
|
||||
if (self::isValidCountryCode($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return self::countryCodeFromIso2($value) ?? '+90';
|
||||
}
|
||||
|
||||
public static function isValidCountryCode(null | string $value): bool
|
||||
{
|
||||
if (! filled($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::countries()->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<int, array{country_code: string, iso2: string, label: string, english_label: string}>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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": {
|
||||
|
||||
1843
config/money.php
Normal file
1843
config/money.php
Normal file
File diff suppressed because it is too large
Load Diff
569
config/tallcms.php
Normal file
569
config/tallcms.php
Normal file
@ -0,0 +1,569 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| TallCMS Version
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The current version of TallCMS. Read dynamically from composer.json
|
||||
| to ensure it's always in sync with the installed package version.
|
||||
|
|
||||
*/
|
||||
'version' => (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'),
|
||||
],
|
||||
];
|
||||
8
config/theme.php
Normal file
8
config/theme.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'active' => env('TALLCMS_THEME_ACTIVE', 'talldaisy'),
|
||||
'themes_path' => base_path('themes'),
|
||||
'cache_themes' => env('TALLCMS_THEME_CACHE', true),
|
||||
'auto_discover' => true,
|
||||
];
|
||||
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
use Spatie\LaravelSettings\Migrations\SettingsMigration;
|
||||
|
||||
return new class extends SettingsMigration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->migrator->add('general.default_country_code', '+90');
|
||||
}
|
||||
};
|
||||
1
public/themes/talldaisy
Symbolic link
1
public/themes/talldaisy
Symbolic link
@ -0,0 +1 @@
|
||||
../../vendor/tallcms/cms/resources/themes/talldaisy/public
|
||||
Loading…
Reference in New Issue
Block a user