Merge pull request 'DX audit and fix (PHP)' (#10) from agent/dx-audit-and-fix--laravel-php-package into dev
Reviewed-on: #10
This commit is contained in:
commit
8f2590477c
137 changed files with 654 additions and 448 deletions
|
|
@ -71,7 +71,6 @@ src/Core/Lang/ # Translation system with ICU + locale fallback chains
|
|||
src/Core/Media/ # Media handling with thumbnail helpers
|
||||
src/Core/Search/ # Search functionality
|
||||
src/Core/Seo/ # SEO utilities
|
||||
src/Core/Service/ # Service discovery and dependency resolution
|
||||
src/Core/Storage/ # Storage with Redis circuit breaker + fallback
|
||||
src/Core/Webhook/ # Webhook system + CronTrigger scheduled action
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Core\Activity\Models\Activity;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|
|
@ -449,7 +451,7 @@ return [
|
|||
// Custom Activity model class (optional).
|
||||
// Set this to use a custom Activity model with additional scopes.
|
||||
// Default: Core\Activity\Models\Activity::class
|
||||
'activity_model' => env('CORE_ACTIVITY_MODEL', \Core\Activity\Models\Activity::class),
|
||||
'activity_model' => env('CORE_ACTIVITY_MODEL', Activity::class),
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ class ScheduleServiceProvider extends ServiceProvider
|
|||
}
|
||||
|
||||
// Verify the class uses the Action trait
|
||||
if (! in_array(\Core\Actions\Action::class, class_uses_recursive($class), true)) {
|
||||
if (! in_array(Action::class, class_uses_recursive($class), true)) {
|
||||
logger()->warning("Scheduled action {$class} does not use the Action trait — skipping");
|
||||
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Core\Actions;
|
|||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* Represents a scheduled action persisted in the database.
|
||||
|
|
@ -24,10 +25,10 @@ use Illuminate\Database\Eloquent\Model;
|
|||
* @property bool $without_overlapping
|
||||
* @property bool $run_in_background
|
||||
* @property bool $is_enabled
|
||||
* @property \Illuminate\Support\Carbon|null $last_run_at
|
||||
* @property \Illuminate\Support\Carbon|null $next_run_at
|
||||
* @property \Illuminate\Support\Carbon $created_at
|
||||
* @property \Illuminate\Support\Carbon $updated_at
|
||||
* @property Carbon|null $last_run_at
|
||||
* @property Carbon|null $next_run_at
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
*/
|
||||
class ScheduledAction extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Actions;
|
||||
|
||||
use Core\ModuleScanner;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use ReflectionClass;
|
||||
|
|
@ -24,7 +25,7 @@ use ReflectionClass;
|
|||
* It uses PHP's native reflection to read attributes — no file parsing.
|
||||
*
|
||||
* @see Scheduled The attribute this scanner discovers
|
||||
* @see \Core\ModuleScanner Similar pattern for Boot.php discovery
|
||||
* @see ModuleScanner Similar pattern for Boot.php discovery
|
||||
*/
|
||||
class ScheduledActionScanner
|
||||
{
|
||||
|
|
@ -32,7 +33,7 @@ class ScheduledActionScanner
|
|||
* Scan directories for classes with #[Scheduled] attribute.
|
||||
*
|
||||
* @param array<string> $paths Directories to scan recursively
|
||||
* @return array<class-string, Scheduled> Map of class name to attribute instance
|
||||
* @return array<class-string, Scheduled> Map of class name to attribute instance
|
||||
*/
|
||||
public function scan(array $paths): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Activity\Concerns;
|
||||
|
||||
use Spatie\Activitylog\Contracts\Activity;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
use Spatie\Activitylog\Traits\LogsActivity as SpatieLogsActivity;
|
||||
|
||||
|
|
@ -77,7 +78,7 @@ trait LogsActivity
|
|||
/**
|
||||
* Tap into the activity before it's saved to add workspace_id.
|
||||
*/
|
||||
public function tapActivity(\Spatie\Activitylog\Contracts\Activity $activity, string $eventName): void
|
||||
public function tapActivity(Activity $activity, string $eventName): void
|
||||
{
|
||||
if ($this->shouldIncludeWorkspace()) {
|
||||
$workspaceId = $this->getActivityWorkspaceId();
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Core\Activity\Console;
|
|||
|
||||
use Core\Activity\Services\ActivityLogService;
|
||||
use Illuminate\Console\Command;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
/**
|
||||
* Command to prune old activity logs.
|
||||
|
|
@ -48,7 +49,7 @@ class ActivityPruneCommand extends Command
|
|||
|
||||
if ($this->option('dry-run')) {
|
||||
// Count without deleting
|
||||
$activityModel = config('core.activity.activity_model', \Spatie\Activitylog\Models\Activity::class);
|
||||
$activityModel = config('core.activity.activity_model', Activity::class);
|
||||
$count = $activityModel::where('created_at', '<', $cutoffDate)->count();
|
||||
|
||||
$this->info("Would delete {$count} activity records.");
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||
namespace Core\Activity\Models;
|
||||
|
||||
use Core\Activity\Scopes\ActivityScopes;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Activitylog\Models\Activity as SpatieActivity;
|
||||
|
||||
/**
|
||||
|
|
@ -81,9 +82,9 @@ class Activity extends SpatieActivity
|
|||
/**
|
||||
* Get the changed attributes.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection<string, array{old: mixed, new: mixed}>
|
||||
* @return Collection<string, array{old: mixed, new: mixed}>
|
||||
*/
|
||||
public function getChangesAttribute(): \Illuminate\Support\Collection
|
||||
public function getChangesAttribute(): Collection
|
||||
{
|
||||
$old = $this->old_values;
|
||||
$new = $this->new_values;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||
namespace Core\Activity\View\Modal\Admin;
|
||||
|
||||
use Core\Activity\Services\ActivityLogService;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Attributes\Url;
|
||||
|
|
@ -362,7 +363,7 @@ class ActivityFeed extends Component
|
|||
};
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('core.activity::admin.activity-feed');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace Core;
|
|||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
|
||||
/**
|
||||
* Application bootstrap - configures Laravel with Core framework patterns.
|
||||
|
|
@ -36,16 +37,16 @@ class Boot
|
|||
*/
|
||||
public static array $providers = [
|
||||
// Lifecycle events - must load first to wire lazy listeners
|
||||
\Core\LifecycleEventProvider::class,
|
||||
LifecycleEventProvider::class,
|
||||
|
||||
// Websites - domain-scoped, must wire before frontages fire events
|
||||
\Core\Website\Boot::class,
|
||||
Website\Boot::class,
|
||||
|
||||
// Core frontages - fire lifecycle events
|
||||
\Core\Front\Boot::class,
|
||||
Front\Boot::class,
|
||||
|
||||
// Base modules (from core-php package)
|
||||
\Core\Mod\Boot::class,
|
||||
Mod\Boot::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -58,7 +59,7 @@ class Boot
|
|||
->withMiddleware(function (Middleware $middleware): void {
|
||||
// Session middleware priority
|
||||
$middleware->priority([
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
StartSession::class,
|
||||
]);
|
||||
|
||||
$middleware->redirectGuestsTo('/login');
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Bouncer\Gate\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
|
|
@ -29,9 +31,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
* @property string $source How this was created ('trained', 'seeded', 'manual')
|
||||
* @property string|null $trained_route The route used during training
|
||||
* @property int|null $trained_by User ID who trained this action
|
||||
* @property \Carbon\Carbon|null $trained_at When training occurred
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property Carbon|null $trained_at When training occurred
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
*/
|
||||
class ActionPermission extends Model
|
||||
{
|
||||
|
|
@ -174,9 +176,9 @@ class ActionPermission extends Model
|
|||
/**
|
||||
* Get all actions for a guard.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, self>
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public static function forGuard(string $guard): \Illuminate\Database\Eloquent\Collection
|
||||
public static function forGuard(string $guard): Collection
|
||||
{
|
||||
return static::where('guard', $guard)->get();
|
||||
}
|
||||
|
|
@ -184,9 +186,9 @@ class ActionPermission extends Model
|
|||
/**
|
||||
* Get all allowed actions for a guard/role combination.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, self>
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public static function allowedFor(string $guard, ?string $role = null): \Illuminate\Database\Eloquent\Collection
|
||||
public static function allowedFor(string $guard, ?string $role = null): Collection
|
||||
{
|
||||
$query = static::where('guard', $guard)
|
||||
->where('allowed', true);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Bouncer\Gate\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
|
|
@ -30,8 +32,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
* @property string|null $ip_address Client IP
|
||||
* @property string $status Result: 'allowed', 'denied', 'pending'
|
||||
* @property bool $was_trained Whether this request triggered training
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
*/
|
||||
class ActionRequest extends Model
|
||||
{
|
||||
|
|
@ -103,9 +105,9 @@ class ActionRequest extends Model
|
|||
/**
|
||||
* Get pending requests (for training review).
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, self>
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public static function pending(): \Illuminate\Database\Eloquent\Collection
|
||||
public static function pending(): Collection
|
||||
{
|
||||
return static::where('status', self::STATUS_PENDING)
|
||||
->orderBy('created_at', 'desc')
|
||||
|
|
@ -115,9 +117,9 @@ class ActionRequest extends Model
|
|||
/**
|
||||
* Get denied requests for an action.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, self>
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public static function deniedFor(string $action): \Illuminate\Database\Eloquent\Collection
|
||||
public static function deniedFor(string $action): Collection
|
||||
{
|
||||
return static::where('action', $action)
|
||||
->where('status', self::STATUS_DENIED)
|
||||
|
|
@ -128,9 +130,9 @@ class ActionRequest extends Model
|
|||
/**
|
||||
* Get requests by user.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, self>
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public static function forUser(int $userId): \Illuminate\Database\Eloquent\Collection
|
||||
public static function forUser(int $userId): Collection
|
||||
{
|
||||
return static::where('user_id', $userId)
|
||||
->orderBy('created_at', 'desc')
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Core\Bouncer\Gate\Tests\Feature;
|
|||
|
||||
use Core\Bouncer\Gate\ActionGateService;
|
||||
use Core\Bouncer\Gate\Attributes\Action;
|
||||
use Core\Bouncer\Gate\Boot;
|
||||
use Core\Bouncer\Gate\Models\ActionPermission;
|
||||
use Core\Bouncer\Gate\Models\ActionRequest;
|
||||
use Core\Bouncer\Gate\RouteActionMacro;
|
||||
|
|
@ -40,7 +41,7 @@ class ActionGateTest extends TestCase
|
|||
protected function getPackageProviders($app): array
|
||||
{
|
||||
return [
|
||||
\Core\Bouncer\Gate\Boot::class,
|
||||
Boot::class,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Cdn;
|
||||
|
||||
use App\Facades\Cdn;
|
||||
use App\Http\Middleware\RewriteOffloadedUrls;
|
||||
use App\Jobs\PushAssetToCdn;
|
||||
use App\Traits\HasCdnUrls;
|
||||
use Core\Cdn\Console\CdnPurge;
|
||||
use Core\Cdn\Console\OffloadMigrateCommand;
|
||||
use Core\Cdn\Console\PushAssetsToCdn;
|
||||
|
|
@ -21,6 +25,9 @@ use Core\Cdn\Services\BunnyStorageService;
|
|||
use Core\Cdn\Services\FluxCdnService;
|
||||
use Core\Cdn\Services\StorageOffload;
|
||||
use Core\Cdn\Services\StorageUrlResolver;
|
||||
use Core\Crypt\LthnHash;
|
||||
use Core\Plug\Cdn\CdnManager;
|
||||
use Core\Plug\Storage\StorageManager;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
/**
|
||||
|
|
@ -45,11 +52,11 @@ class Boot extends ServiceProvider
|
|||
$this->mergeConfigFrom(__DIR__.'/offload.php', 'offload');
|
||||
|
||||
// Register Plug managers as singletons (when available)
|
||||
if (class_exists(\Core\Plug\Cdn\CdnManager::class)) {
|
||||
$this->app->singleton(\Core\Plug\Cdn\CdnManager::class);
|
||||
if (class_exists(CdnManager::class)) {
|
||||
$this->app->singleton(CdnManager::class);
|
||||
}
|
||||
if (class_exists(\Core\Plug\Storage\StorageManager::class)) {
|
||||
$this->app->singleton(\Core\Plug\Storage\StorageManager::class);
|
||||
if (class_exists(StorageManager::class)) {
|
||||
$this->app->singleton(StorageManager::class);
|
||||
}
|
||||
|
||||
// Register legacy services as singletons (for backward compatibility)
|
||||
|
|
@ -115,32 +122,32 @@ class Boot extends ServiceProvider
|
|||
|
||||
// Crypt
|
||||
if (! class_exists(\App\Services\Crypt\LthnHash::class)) {
|
||||
class_alias(\Core\Crypt\LthnHash::class, \App\Services\Crypt\LthnHash::class);
|
||||
class_alias(LthnHash::class, \App\Services\Crypt\LthnHash::class);
|
||||
}
|
||||
|
||||
// Models
|
||||
if (! class_exists(\App\Models\StorageOffload::class)) {
|
||||
class_alias(\Core\Cdn\Models\StorageOffload::class, \App\Models\StorageOffload::class);
|
||||
class_alias(Models\StorageOffload::class, \App\Models\StorageOffload::class);
|
||||
}
|
||||
|
||||
// Facades
|
||||
if (! class_exists(\App\Facades\Cdn::class)) {
|
||||
class_alias(\Core\Cdn\Facades\Cdn::class, \App\Facades\Cdn::class);
|
||||
if (! class_exists(Cdn::class)) {
|
||||
class_alias(Facades\Cdn::class, Cdn::class);
|
||||
}
|
||||
|
||||
// Traits
|
||||
if (! trait_exists(\App\Traits\HasCdnUrls::class)) {
|
||||
class_alias(\Core\Cdn\Traits\HasCdnUrls::class, \App\Traits\HasCdnUrls::class);
|
||||
if (! trait_exists(HasCdnUrls::class)) {
|
||||
class_alias(Traits\HasCdnUrls::class, HasCdnUrls::class);
|
||||
}
|
||||
|
||||
// Middleware
|
||||
if (! class_exists(\App\Http\Middleware\RewriteOffloadedUrls::class)) {
|
||||
class_alias(\Core\Cdn\Middleware\RewriteOffloadedUrls::class, \App\Http\Middleware\RewriteOffloadedUrls::class);
|
||||
if (! class_exists(RewriteOffloadedUrls::class)) {
|
||||
class_alias(Middleware\RewriteOffloadedUrls::class, RewriteOffloadedUrls::class);
|
||||
}
|
||||
|
||||
// Jobs
|
||||
if (! class_exists(\App\Jobs\PushAssetToCdn::class)) {
|
||||
class_alias(\Core\Cdn\Jobs\PushAssetToCdn::class, \App\Jobs\PushAssetToCdn::class);
|
||||
if (! class_exists(PushAssetToCdn::class)) {
|
||||
class_alias(Jobs\PushAssetToCdn::class, PushAssetToCdn::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Cdn\Console;
|
||||
|
||||
use Core\Plug\Cdn\Bunny\Purge;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CdnPurge extends Command
|
||||
|
|
@ -43,8 +45,8 @@ class CdnPurge extends Command
|
|||
{
|
||||
parent::__construct();
|
||||
|
||||
if (class_exists(\Core\Plug\Cdn\Bunny\Purge::class)) {
|
||||
$this->purger = new \Core\Plug\Cdn\Bunny\Purge;
|
||||
if (class_exists(Purge::class)) {
|
||||
$this->purger = new Purge;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -96,8 +98,8 @@ class CdnPurge extends Command
|
|||
// Purge by workspace
|
||||
if (empty($workspaceArg)) {
|
||||
$workspaceOptions = ['all', 'Select specific URLs'];
|
||||
if (class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
$workspaceOptions = array_merge($workspaceOptions, \Core\Tenant\Models\Workspace::pluck('slug')->toArray());
|
||||
if (class_exists(Workspace::class)) {
|
||||
$workspaceOptions = array_merge($workspaceOptions, Workspace::pluck('slug')->toArray());
|
||||
}
|
||||
$workspaceArg = $this->choice(
|
||||
'What would you like to purge?',
|
||||
|
|
@ -218,13 +220,13 @@ class CdnPurge extends Command
|
|||
|
||||
protected function purgeAllWorkspaces(bool $dryRun): int
|
||||
{
|
||||
if (! class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
if (! class_exists(Workspace::class)) {
|
||||
$this->error('Workspace purge requires Tenant module to be installed.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$workspaces = \Core\Tenant\Models\Workspace::all();
|
||||
$workspaces = Workspace::all();
|
||||
|
||||
if ($workspaces->isEmpty()) {
|
||||
$this->error('No workspaces found');
|
||||
|
|
@ -276,19 +278,19 @@ class CdnPurge extends Command
|
|||
|
||||
protected function purgeWorkspace(string $slug, bool $dryRun): int
|
||||
{
|
||||
if (! class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
if (! class_exists(Workspace::class)) {
|
||||
$this->error('Workspace purge requires Tenant module to be installed.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$workspace = \Core\Tenant\Models\Workspace::where('slug', $slug)->first();
|
||||
$workspace = Workspace::where('slug', $slug)->first();
|
||||
|
||||
if (! $workspace) {
|
||||
$this->error("Workspace not found: {$slug}");
|
||||
$this->newLine();
|
||||
$this->info('Available workspaces:');
|
||||
\Core\Tenant\Models\Workspace::pluck('slug')->each(fn ($s) => $this->line(" - {$s}"));
|
||||
Workspace::pluck('slug')->each(fn ($s) => $this->line(" - {$s}"));
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Core\Cdn\Console;
|
|||
|
||||
use Core\Cdn\Services\FluxCdnService;
|
||||
use Core\Cdn\Services\StorageUrlResolver;
|
||||
use Core\Plug\Storage\Bunny\VBucket;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ class PushAssetsToCdn extends Command
|
|||
|
||||
public function handle(FluxCdnService $flux, StorageUrlResolver $cdn): int
|
||||
{
|
||||
if (! class_exists(\Core\Plug\Storage\Bunny\VBucket::class)) {
|
||||
if (! class_exists(VBucket::class)) {
|
||||
$this->error('Push assets to CDN requires Core\Plug\Storage\Bunny\VBucket class. Plug module not installed.');
|
||||
|
||||
return self::FAILURE;
|
||||
|
|
@ -54,7 +55,7 @@ class PushAssetsToCdn extends Command
|
|||
|
||||
// Create vBucket for workspace isolation
|
||||
$domain = $this->option('domain');
|
||||
$this->vbucket = \Core\Plug\Storage\Bunny\VBucket::public($domain);
|
||||
$this->vbucket = VBucket::public($domain);
|
||||
|
||||
$pushFlux = $this->option('flux');
|
||||
$pushFontawesome = $this->option('fontawesome');
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ use Illuminate\Support\Facades\Facade;
|
|||
* @method static string vBucketPath(string $domain, string $path)
|
||||
* @method static array vBucketUrls(string $domain, string $path)
|
||||
*
|
||||
* @see \Core\Cdn\Services\StorageUrlResolver
|
||||
* @see StorageUrlResolver
|
||||
*/
|
||||
class Cdn extends Facade
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Cdn\Jobs;
|
||||
|
||||
use Core\Plug\Storage\StorageManager;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
|
@ -60,7 +61,7 @@ class PushAssetToCdn implements ShouldQueue
|
|||
*/
|
||||
public function handle(?object $storage = null): void
|
||||
{
|
||||
if (! class_exists(\Core\Plug\Storage\StorageManager::class)) {
|
||||
if (! class_exists(StorageManager::class)) {
|
||||
Log::warning('PushAssetToCdn: StorageManager not available, Plug module not installed');
|
||||
|
||||
return;
|
||||
|
|
@ -68,7 +69,7 @@ class PushAssetToCdn implements ShouldQueue
|
|||
|
||||
// Resolve from container if not injected
|
||||
if ($storage === null) {
|
||||
$storage = app(\Core\Plug\Storage\StorageManager::class);
|
||||
$storage = app(StorageManager::class);
|
||||
}
|
||||
|
||||
if (! config('cdn.bunny.push_enabled', false)) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Core\Cdn\Models;
|
|||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* Tracks files that have been offloaded to remote storage.
|
||||
|
|
@ -26,9 +27,9 @@ use Illuminate\Database\Eloquent\Model;
|
|||
* @property string|null $mime_type MIME type
|
||||
* @property string|null $category Category for path prefixing
|
||||
* @property array|null $metadata Additional metadata
|
||||
* @property \Illuminate\Support\Carbon|null $offloaded_at When file was offloaded
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property Carbon|null $offloaded_at When file was offloaded
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
*/
|
||||
class StorageOffload extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Core\Cdn\Services;
|
|||
|
||||
use Core\Cdn\Jobs\PushAssetToCdn;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
|
|
@ -339,7 +340,7 @@ class AssetPipeline
|
|||
PushAssetToCdn::dispatch($disk, $path, $zone);
|
||||
} elseif ($this->storage !== null) {
|
||||
// Synchronous push if no queue configured (requires StorageManager from Plug module)
|
||||
$diskInstance = \Illuminate\Support\Facades\Storage::disk($disk);
|
||||
$diskInstance = Storage::disk($disk);
|
||||
if ($diskInstance->exists($path)) {
|
||||
$contents = $diskInstance->get($path);
|
||||
$this->storage->zone($zone)->upload()->contents($path, $contents);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||
namespace Core\Cdn\Services;
|
||||
|
||||
use Core\Helpers\Cdn;
|
||||
use Flux\AssetManager;
|
||||
use Flux\Flux;
|
||||
|
||||
/**
|
||||
|
|
@ -83,7 +84,7 @@ class FluxCdnService
|
|||
|
||||
// Use CDN when enabled (respects CDN_FORCE_LOCAL for testing)
|
||||
if (! $this->shouldUseCdn()) {
|
||||
return \Flux\AssetManager::editorScripts();
|
||||
return AssetManager::editorScripts();
|
||||
}
|
||||
|
||||
// In production, use CDN URL (no vBucket - shared platform asset)
|
||||
|
|
@ -109,7 +110,7 @@ class FluxCdnService
|
|||
|
||||
// Use CDN when enabled (respects CDN_FORCE_LOCAL for testing)
|
||||
if (! $this->shouldUseCdn()) {
|
||||
return \Flux\AssetManager::editorStyles();
|
||||
return AssetManager::editorStyles();
|
||||
}
|
||||
|
||||
// In production, use CDN URL (no vBucket - shared platform asset)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Core\Cdn\Services;
|
|||
|
||||
use Core\Cdn\Models\StorageOffload as OffloadModel;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
|
@ -305,7 +306,7 @@ class StorageOffload
|
|||
/**
|
||||
* Get all offloaded files for a category.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection<OffloadModel>
|
||||
* @return Collection<OffloadModel>
|
||||
*/
|
||||
public function getByCategory(string $category)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ declare(strict_types=1);
|
|||
namespace Core\Cdn\Services;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Core\Cdn\Jobs\PushAssetToCdn;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
|
@ -372,7 +374,7 @@ class StorageUrlResolver
|
|||
/**
|
||||
* Get the public storage disk.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
* @return Filesystem
|
||||
*/
|
||||
public function publicDisk()
|
||||
{
|
||||
|
|
@ -382,7 +384,7 @@ class StorageUrlResolver
|
|||
/**
|
||||
* Get the private storage disk.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
* @return Filesystem
|
||||
*/
|
||||
public function privateDisk()
|
||||
{
|
||||
|
|
@ -403,7 +405,7 @@ class StorageUrlResolver
|
|||
if ($stored && $pushToCdn && config('cdn.pipeline.auto_push', true)) {
|
||||
// Queue the push if configured, otherwise push synchronously
|
||||
if ($queue = config('cdn.pipeline.queue')) {
|
||||
dispatch(new \Core\Cdn\Jobs\PushAssetToCdn('hetzner-public', $path, 'public'))->onQueue($queue);
|
||||
dispatch(new PushAssetToCdn('hetzner-public', $path, 'public'))->onQueue($queue);
|
||||
} else {
|
||||
$this->pushToCdn('hetzner-public', $path, 'public');
|
||||
}
|
||||
|
|
@ -425,7 +427,7 @@ class StorageUrlResolver
|
|||
|
||||
if ($stored && $pushToCdn && config('cdn.pipeline.auto_push', true)) {
|
||||
if ($queue = config('cdn.pipeline.queue')) {
|
||||
dispatch(new \Core\Cdn\Jobs\PushAssetToCdn('hetzner-private', $path, 'private'))->onQueue($queue);
|
||||
dispatch(new PushAssetToCdn('hetzner-private', $path, 'private'))->onQueue($queue);
|
||||
} else {
|
||||
$this->pushToCdn('hetzner-private', $path, 'private');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@ declare(strict_types=1);
|
|||
namespace Core\Config\Console;
|
||||
|
||||
use Core\Config\ConfigExporter;
|
||||
use Core\Config\Models\ConfigKey;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
|
||||
/**
|
||||
* Export config to JSON or YAML file.
|
||||
|
|
@ -45,13 +48,13 @@ class ConfigExportCommand extends Command
|
|||
// Resolve workspace
|
||||
$workspace = null;
|
||||
if ($workspaceSlug) {
|
||||
if (! class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
if (! class_exists(Workspace::class)) {
|
||||
$this->components->error('Tenant module not installed. Cannot export workspace config.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$workspace = \Core\Tenant\Models\Workspace::where('slug', $workspaceSlug)->first();
|
||||
$workspace = Workspace::where('slug', $workspaceSlug)->first();
|
||||
|
||||
if (! $workspace) {
|
||||
$this->components->error("Workspace not found: {$workspaceSlug}");
|
||||
|
|
@ -96,16 +99,16 @@ class ConfigExportCommand extends Command
|
|||
/**
|
||||
* Get autocompletion suggestions.
|
||||
*/
|
||||
public function complete(CompletionInput $input, \Symfony\Component\Console\Completion\CompletionSuggestions $suggestions): void
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestOptionValuesFor('workspace')) {
|
||||
if (class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
$suggestions->suggestValues(\Core\Tenant\Models\Workspace::pluck('slug')->toArray());
|
||||
if (class_exists(Workspace::class)) {
|
||||
$suggestions->suggestValues(Workspace::pluck('slug')->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
if ($input->mustSuggestOptionValuesFor('category')) {
|
||||
$suggestions->suggestValues(\Core\Config\Models\ConfigKey::distinct()->pluck('category')->toArray());
|
||||
$suggestions->suggestValues(ConfigKey::distinct()->pluck('category')->toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@ namespace Core\Config\Console;
|
|||
|
||||
use Core\Config\ConfigExporter;
|
||||
use Core\Config\ConfigVersioning;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
|
||||
/**
|
||||
* Import config from JSON or YAML file.
|
||||
|
|
@ -53,13 +55,13 @@ class ConfigImportCommand extends Command
|
|||
// Resolve workspace
|
||||
$workspace = null;
|
||||
if ($workspaceSlug) {
|
||||
if (! class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
if (! class_exists(Workspace::class)) {
|
||||
$this->components->error('Tenant module not installed. Cannot import workspace config.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$workspace = \Core\Tenant\Models\Workspace::where('slug', $workspaceSlug)->first();
|
||||
$workspace = Workspace::where('slug', $workspaceSlug)->first();
|
||||
|
||||
if (! $workspace) {
|
||||
$this->components->error("Workspace not found: {$workspaceSlug}");
|
||||
|
|
@ -174,11 +176,11 @@ class ConfigImportCommand extends Command
|
|||
/**
|
||||
* Get autocompletion suggestions.
|
||||
*/
|
||||
public function complete(CompletionInput $input, \Symfony\Component\Console\Completion\CompletionSuggestions $suggestions): void
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestOptionValuesFor('workspace')) {
|
||||
if (class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
$suggestions->suggestValues(\Core\Tenant\Models\Workspace::pluck('slug')->toArray());
|
||||
if (class_exists(Workspace::class)) {
|
||||
$suggestions->suggestValues(Workspace::pluck('slug')->toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Core\Config\Console;
|
|||
|
||||
use Core\Config\ConfigService;
|
||||
use Core\Config\Models\ConfigKey;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ConfigListCommand extends Command
|
||||
|
|
@ -33,13 +34,13 @@ class ConfigListCommand extends Command
|
|||
$workspace = null;
|
||||
|
||||
if ($workspaceSlug) {
|
||||
if (! class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
if (! class_exists(Workspace::class)) {
|
||||
$this->error('Tenant module not installed. Cannot filter by workspace.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$workspace = \Core\Tenant\Models\Workspace::where('slug', $workspaceSlug)->first();
|
||||
$workspace = Workspace::where('slug', $workspaceSlug)->first();
|
||||
|
||||
if (! $workspace) {
|
||||
$this->error("Workspace not found: {$workspaceSlug}");
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||
namespace Core\Config\Console;
|
||||
|
||||
use Core\Config\ConfigService;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ConfigPrimeCommand extends Command
|
||||
|
|
@ -36,13 +37,13 @@ class ConfigPrimeCommand extends Command
|
|||
}
|
||||
|
||||
if ($workspaceSlug) {
|
||||
if (! class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
if (! class_exists(Workspace::class)) {
|
||||
$this->error('Tenant module not installed. Cannot prime workspace config.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$workspace = \Core\Tenant\Models\Workspace::where('slug', $workspaceSlug)->first();
|
||||
$workspace = Workspace::where('slug', $workspaceSlug)->first();
|
||||
|
||||
if (! $workspace) {
|
||||
$this->error("Workspace not found: {$workspaceSlug}");
|
||||
|
|
@ -59,7 +60,7 @@ class ConfigPrimeCommand extends Command
|
|||
|
||||
$this->info('Priming config cache for all workspaces...');
|
||||
|
||||
if (! class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
if (! class_exists(Workspace::class)) {
|
||||
$this->warn('Tenant module not installed. Only priming system config.');
|
||||
$config->prime(null);
|
||||
$this->info('System config cached.');
|
||||
|
|
@ -67,7 +68,7 @@ class ConfigPrimeCommand extends Command
|
|||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$this->withProgressBar(\Core\Tenant\Models\Workspace::all(), function ($workspace) use ($config) {
|
||||
$this->withProgressBar(Workspace::all(), function ($workspace) use ($config) {
|
||||
$config->prime($workspace);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,11 @@ namespace Core\Config\Console;
|
|||
|
||||
use Core\Config\ConfigVersioning;
|
||||
use Core\Config\Models\ConfigVersion;
|
||||
use Core\Config\VersionDiff;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
|
||||
/**
|
||||
* Manage config versions.
|
||||
|
|
@ -50,13 +53,13 @@ class ConfigVersionCommand extends Command
|
|||
// Resolve workspace
|
||||
$workspace = null;
|
||||
if ($workspaceSlug) {
|
||||
if (! class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
if (! class_exists(Workspace::class)) {
|
||||
$this->components->error('Tenant module not installed. Cannot manage workspace versions.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$workspace = \Core\Tenant\Models\Workspace::where('slug', $workspaceSlug)->first();
|
||||
$workspace = Workspace::where('slug', $workspaceSlug)->first();
|
||||
|
||||
if (! $workspace) {
|
||||
$this->components->error("Workspace not found: {$workspaceSlug}");
|
||||
|
|
@ -282,7 +285,7 @@ class ConfigVersionCommand extends Command
|
|||
/**
|
||||
* Display a diff.
|
||||
*/
|
||||
protected function displayDiff(\Core\Config\VersionDiff $diff): void
|
||||
protected function displayDiff(VersionDiff $diff): void
|
||||
{
|
||||
$this->components->info("Summary: {$diff->getSummary()}");
|
||||
$this->newLine();
|
||||
|
|
@ -402,15 +405,15 @@ class ConfigVersionCommand extends Command
|
|||
/**
|
||||
* Get autocompletion suggestions.
|
||||
*/
|
||||
public function complete(CompletionInput $input, \Symfony\Component\Console\Completion\CompletionSuggestions $suggestions): void
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestArgumentValuesFor('action')) {
|
||||
$suggestions->suggestValues(['list', 'create', 'show', 'rollback', 'compare', 'diff', 'delete']);
|
||||
}
|
||||
|
||||
if ($input->mustSuggestOptionValuesFor('workspace')) {
|
||||
if (class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
$suggestions->suggestValues(\Core\Tenant\Models\Workspace::pluck('slug')->toArray());
|
||||
if (class_exists(Workspace::class)) {
|
||||
$suggestions->suggestValues(Workspace::pluck('slug')->toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Config\Contracts;
|
||||
|
||||
use Core\Config\ConfigResolver;
|
||||
use Core\Config\Models\Channel;
|
||||
|
||||
/**
|
||||
|
|
@ -72,7 +73,7 @@ use Core\Config\Models\Channel;
|
|||
* ```
|
||||
*
|
||||
*
|
||||
* @see \Core\Config\ConfigResolver::registerProvider()
|
||||
* @see ConfigResolver::registerProvider()
|
||||
*/
|
||||
interface ConfigProvider
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Config\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
|
@ -35,8 +37,8 @@ use Illuminate\Support\Facades\Log;
|
|||
* @property int|null $parent_id
|
||||
* @property int|null $workspace_id
|
||||
* @property array|null $metadata
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
*/
|
||||
class Channel extends Model
|
||||
{
|
||||
|
|
@ -77,8 +79,8 @@ class Channel extends Model
|
|||
*/
|
||||
public function workspace(): BelongsTo
|
||||
{
|
||||
if (class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
return $this->belongsTo(\Core\Tenant\Models\Workspace::class);
|
||||
if (class_exists(Workspace::class)) {
|
||||
return $this->belongsTo(Workspace::class);
|
||||
}
|
||||
|
||||
// Return a null relationship when Tenant module is not installed
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Config\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Core\Config\Enums\ConfigType;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
|
@ -30,8 +32,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||
* @property string|null $description
|
||||
* @property mixed $default_value
|
||||
* @property bool $is_sensitive
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
*/
|
||||
class ConfigKey extends Model
|
||||
{
|
||||
|
|
@ -108,9 +110,9 @@ class ConfigKey extends Model
|
|||
/**
|
||||
* Get all keys for a category.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, self>
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public static function forCategory(string $category): \Illuminate\Database\Eloquent\Collection
|
||||
public static function forCategory(string $category): Collection
|
||||
{
|
||||
return static::where('category', $category)->get();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Config\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Core\Config\Enums\ScopeType;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
|
@ -29,9 +31,9 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
|||
* @property int|null $scope_id
|
||||
* @property int|null $parent_profile_id
|
||||
* @property int $priority
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property \Carbon\Carbon|null $deleted_at
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property Carbon|null $deleted_at
|
||||
*/
|
||||
class ConfigProfile extends Model
|
||||
{
|
||||
|
|
@ -90,9 +92,9 @@ class ConfigProfile extends Model
|
|||
/**
|
||||
* Get profiles for a scope.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, self>
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public static function forScope(ScopeType $type, ?int $scopeId = null): \Illuminate\Database\Eloquent\Collection
|
||||
public static function forScope(ScopeType $type, ?int $scopeId = null): Collection
|
||||
{
|
||||
return static::where('scope_type', $type)
|
||||
->where('scope_id', $scopeId)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Config\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Core\Config\ConfigResult;
|
||||
use Core\Config\Enums\ConfigType;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
|
|
@ -36,7 +39,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
* @property int|null $source_profile_id
|
||||
* @property int|null $source_channel_id
|
||||
* @property bool $virtual
|
||||
* @property \Carbon\Carbon $computed_at
|
||||
* @property Carbon $computed_at
|
||||
*/
|
||||
class ConfigResolved extends Model
|
||||
{
|
||||
|
|
@ -71,8 +74,8 @@ class ConfigResolved extends Model
|
|||
*/
|
||||
public function workspace(): BelongsTo
|
||||
{
|
||||
if (class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
return $this->belongsTo(\Core\Tenant\Models\Workspace::class);
|
||||
if (class_exists(Workspace::class)) {
|
||||
return $this->belongsTo(Workspace::class);
|
||||
}
|
||||
|
||||
// Return a null relationship when Tenant module is not installed
|
||||
|
|
@ -155,9 +158,9 @@ class ConfigResolved extends Model
|
|||
/**
|
||||
* Get all resolved config for a scope.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, self>
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public static function forScope(?int $workspaceId = null, ?int $channelId = null): \Illuminate\Database\Eloquent\Collection
|
||||
public static function forScope(?int $workspaceId = null, ?int $channelId = null): Collection
|
||||
{
|
||||
return static::where('workspace_id', $workspaceId)
|
||||
->where('channel_id', $channelId)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Config\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Core\Config\ConfigResolver;
|
||||
use Core\Config\Enums\ScopeType;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
|
@ -31,8 +34,8 @@ use Illuminate\Support\Facades\Crypt;
|
|||
* @property mixed $value
|
||||
* @property bool $locked
|
||||
* @property int|null $inherited_from
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
*/
|
||||
class ConfigValue extends Model
|
||||
{
|
||||
|
|
@ -76,7 +79,7 @@ class ConfigValue extends Model
|
|||
$encrypted = substr($decoded, strlen(self::ENCRYPTED_PREFIX));
|
||||
|
||||
return json_decode(Crypt::decryptString($encrypted), true);
|
||||
} catch (\Illuminate\Contracts\Encryption\DecryptException) {
|
||||
} catch (DecryptException) {
|
||||
// Return null if decryption fails (key rotation, corruption, etc.)
|
||||
return null;
|
||||
}
|
||||
|
|
@ -255,9 +258,9 @@ class ConfigValue extends Model
|
|||
*
|
||||
* @param array<int> $profileIds
|
||||
* @param array<int>|null $channelIds Include null for "all channels" values
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, self>
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public static function forKeyInProfiles(int $keyId, array $profileIds, ?array $channelIds = null): \Illuminate\Database\Eloquent\Collection
|
||||
public static function forKeyInProfiles(int $keyId, array $profileIds, ?array $channelIds = null): Collection
|
||||
{
|
||||
return static::where('key_id', $keyId)
|
||||
->whereIn('profile_id', $profileIds)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Config\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
|
|
@ -26,7 +29,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
* @property string $label
|
||||
* @property string $snapshot
|
||||
* @property string|null $author
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property Carbon $created_at
|
||||
*/
|
||||
class ConfigVersion extends Model
|
||||
{
|
||||
|
|
@ -65,8 +68,8 @@ class ConfigVersion extends Model
|
|||
*/
|
||||
public function workspace(): BelongsTo
|
||||
{
|
||||
if (class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
return $this->belongsTo(\Core\Tenant\Models\Workspace::class);
|
||||
if (class_exists(Workspace::class)) {
|
||||
return $this->belongsTo(Workspace::class);
|
||||
}
|
||||
|
||||
// Return a null relationship when Tenant module is not installed
|
||||
|
|
@ -136,9 +139,9 @@ class ConfigVersion extends Model
|
|||
* Get versions for a scope.
|
||||
*
|
||||
* @param int|null $workspaceId Workspace ID or null for system
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, self>
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public static function forScope(?int $workspaceId = null): \Illuminate\Database\Eloquent\Collection
|
||||
public static function forScope(?int $workspaceId = null): Collection
|
||||
{
|
||||
return static::where('workspace_id', $workspaceId)
|
||||
->orderByDesc('created_at')
|
||||
|
|
@ -160,9 +163,9 @@ class ConfigVersion extends Model
|
|||
/**
|
||||
* Get versions created by a specific author.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, self>
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public static function byAuthor(string $author): \Illuminate\Database\Eloquent\Collection
|
||||
public static function byAuthor(string $author): Collection
|
||||
{
|
||||
return static::where('author', $author)
|
||||
->orderByDesc('created_at')
|
||||
|
|
@ -172,11 +175,11 @@ class ConfigVersion extends Model
|
|||
/**
|
||||
* Get versions created within a date range.
|
||||
*
|
||||
* @param \Carbon\Carbon $from Start date
|
||||
* @param \Carbon\Carbon $to End date
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, self>
|
||||
* @param Carbon $from Start date
|
||||
* @param Carbon $to End date
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public static function inDateRange(\Carbon\Carbon $from, \Carbon\Carbon $to): \Illuminate\Database\Eloquent\Collection
|
||||
public static function inDateRange(Carbon $from, Carbon $to): Collection
|
||||
{
|
||||
return static::whereBetween('created_at', [$from, $to])
|
||||
->orderByDesc('created_at')
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@ use Core\Config\Models\ConfigProfile;
|
|||
use Core\Config\Models\ConfigResolved;
|
||||
use Core\Config\Models\ConfigValue;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
// Clear hash for clean test state
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ use Core\Config\ConfigService;
|
|||
use Core\Config\Models\ConfigKey;
|
||||
use Core\Config\Models\ConfigProfile;
|
||||
use Core\Config\Models\ConfigValue;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
|
|
@ -24,7 +27,7 @@ use Livewire\Component;
|
|||
*
|
||||
* @property-read ConfigProfile $activeProfile
|
||||
* @property-read array<string> $categories
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection $workspaces
|
||||
* @property-read Collection $workspaces
|
||||
*/
|
||||
class ConfigPanel extends Component
|
||||
{
|
||||
|
|
@ -79,17 +82,17 @@ class ConfigPanel extends Component
|
|||
* Get all workspaces (requires Tenant module).
|
||||
*/
|
||||
#[Computed]
|
||||
public function workspaces(): \Illuminate\Database\Eloquent\Collection
|
||||
public function workspaces(): Collection
|
||||
{
|
||||
if (! class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
return new \Illuminate\Database\Eloquent\Collection;
|
||||
if (! class_exists(Workspace::class)) {
|
||||
return new Collection;
|
||||
}
|
||||
|
||||
return \Core\Tenant\Models\Workspace::orderBy('name')->get();
|
||||
return Workspace::orderBy('name')->get();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function keys(): \Illuminate\Database\Eloquent\Collection
|
||||
public function keys(): Collection
|
||||
{
|
||||
return ConfigKey::query()
|
||||
->when($this->category, fn ($q) => $q->where('category', $this->category))
|
||||
|
|
@ -119,8 +122,8 @@ class ConfigPanel extends Component
|
|||
#[Computed]
|
||||
public function selectedWorkspace(): ?object
|
||||
{
|
||||
if ($this->workspaceId && class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
return \Core\Tenant\Models\Workspace::find($this->workspaceId);
|
||||
if ($this->workspaceId && class_exists(Workspace::class)) {
|
||||
return Workspace::find($this->workspaceId);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -271,7 +274,7 @@ class ConfigPanel extends Component
|
|||
$this->dispatch('config-cleared');
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('core.config::admin.config-panel')
|
||||
->layout('hub::admin.layouts.app', ['title' => 'Configuration']);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ use Core\Config\ConfigService;
|
|||
use Core\Config\Models\ConfigKey;
|
||||
use Core\Config\Models\ConfigProfile;
|
||||
use Core\Config\Models\ConfigValue;
|
||||
use Core\Tenant\Services\WorkspaceService;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
|
@ -28,7 +31,7 @@ use Livewire\Component;
|
|||
* @property-read ConfigProfile $systemProfile
|
||||
* @property-read object|null $workspace
|
||||
* @property-read string $prefix
|
||||
* @property-read array<int, array{namespace: string, label: string, keys: \Illuminate\Support\Collection}> $tabs
|
||||
* @property-read array<int, array{namespace: string, label: string, keys: Collection}> $tabs
|
||||
*/
|
||||
class WorkspaceConfig extends Component
|
||||
{
|
||||
|
|
@ -46,8 +49,8 @@ class WorkspaceConfig extends Component
|
|||
$this->config = $config;
|
||||
|
||||
// Try to resolve WorkspaceService if Tenant module is installed
|
||||
if (class_exists(\Core\Tenant\Services\WorkspaceService::class)) {
|
||||
$this->workspaceService = app(\Core\Tenant\Services\WorkspaceService::class);
|
||||
if (class_exists(WorkspaceService::class)) {
|
||||
$this->workspaceService = app(WorkspaceService::class);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -277,7 +280,7 @@ class WorkspaceConfig extends Component
|
|||
$this->dispatch('config-cleared');
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('core.config::admin.workspace-config')
|
||||
->layout('hub::admin.layouts.app', ['title' => 'Settings']);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ namespace Core\Console\Commands;
|
|||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
|
||||
/**
|
||||
* Core PHP Framework Installation Command.
|
||||
|
|
@ -484,8 +486,8 @@ class InstallCommand extends Command
|
|||
* but implements the method for consistency with other commands.
|
||||
*/
|
||||
public function complete(
|
||||
\Symfony\Component\Console\Completion\CompletionInput $input,
|
||||
\Symfony\Component\Console\Completion\CompletionSuggestions $suggestions
|
||||
CompletionInput $input,
|
||||
CompletionSuggestions $suggestions
|
||||
): void {
|
||||
// No argument/option values need completion for this command
|
||||
// All options are flags (--force, --no-interaction, --dry-run)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ namespace Core\Console\Commands;
|
|||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
|
||||
/**
|
||||
* Generate a new module scaffold.
|
||||
|
|
@ -508,8 +510,8 @@ BLADE;
|
|||
* Get shell completion suggestions for arguments.
|
||||
*/
|
||||
public function complete(
|
||||
\Symfony\Component\Console\Completion\CompletionInput $input,
|
||||
\Symfony\Component\Console\Completion\CompletionSuggestions $suggestions
|
||||
CompletionInput $input,
|
||||
CompletionSuggestions $suggestions
|
||||
): void {
|
||||
if ($input->mustSuggestArgumentValuesFor('name')) {
|
||||
// Suggest common module naming patterns
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ namespace Core\Console\Commands;
|
|||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
|
||||
/**
|
||||
* Generate a new Plug provider scaffold.
|
||||
|
|
@ -604,8 +606,8 @@ PHP;
|
|||
* Get shell completion suggestions for arguments and options.
|
||||
*/
|
||||
public function complete(
|
||||
\Symfony\Component\Console\Completion\CompletionInput $input,
|
||||
\Symfony\Component\Console\Completion\CompletionSuggestions $suggestions
|
||||
CompletionInput $input,
|
||||
CompletionSuggestions $suggestions
|
||||
): void {
|
||||
if ($input->mustSuggestArgumentValuesFor('name')) {
|
||||
// Suggest common social platform names
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ namespace Core\Console\Commands;
|
|||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
|
||||
/**
|
||||
* Generate a new Website scaffold.
|
||||
|
|
@ -574,8 +576,8 @@ BLADE;
|
|||
* Get shell completion suggestions for arguments and options.
|
||||
*/
|
||||
public function complete(
|
||||
\Symfony\Component\Console\Completion\CompletionInput $input,
|
||||
\Symfony\Component\Console\Completion\CompletionSuggestions $suggestions
|
||||
CompletionInput $input,
|
||||
CompletionSuggestions $suggestions
|
||||
): void {
|
||||
if ($input->mustSuggestArgumentValuesFor('name')) {
|
||||
// Suggest common website naming patterns
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ use Illuminate\Console\Command;
|
|||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
|
||||
/**
|
||||
* Create a new Core PHP Framework project.
|
||||
|
|
@ -343,8 +345,8 @@ class NewProjectCommand extends Command
|
|||
* Get shell completion suggestions.
|
||||
*/
|
||||
public function complete(
|
||||
\Symfony\Component\Console\Completion\CompletionInput $input,
|
||||
\Symfony\Component\Console\Completion\CompletionSuggestions $suggestions
|
||||
CompletionInput $input,
|
||||
CompletionSuggestions $suggestions
|
||||
): void {
|
||||
if ($input->mustSuggestArgumentValuesFor('name')) {
|
||||
// Suggest common project naming patterns
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ namespace Core\Console\Commands;
|
|||
|
||||
use Core\Mail\EmailShieldStat;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
|
||||
/**
|
||||
* Prune old Email Shield statistics records.
|
||||
|
|
@ -135,8 +137,8 @@ class PruneEmailShieldStatsCommand extends Command
|
|||
* Get shell completion suggestions for options.
|
||||
*/
|
||||
public function complete(
|
||||
\Symfony\Component\Console\Completion\CompletionInput $input,
|
||||
\Symfony\Component\Console\Completion\CompletionSuggestions $suggestions
|
||||
CompletionInput $input,
|
||||
CompletionSuggestions $suggestions
|
||||
): void {
|
||||
if ($input->mustSuggestOptionValuesFor('days')) {
|
||||
// Suggest common retention periods
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ declare(strict_types=1);
|
|||
namespace Core\Crypt;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Database\Eloquent\Casts\ArrayObject;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
|
|
@ -27,7 +29,7 @@ class EncryptArrayObject implements CastsAttributes
|
|||
/**
|
||||
* Cast the given value to an ArrayObject.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param Model $model
|
||||
* @param mixed $value
|
||||
* @param array<string, mixed> $attributes
|
||||
*/
|
||||
|
|
@ -36,7 +38,7 @@ class EncryptArrayObject implements CastsAttributes
|
|||
if (isset($attributes[$key])) {
|
||||
try {
|
||||
$decrypted = Crypt::decryptString($attributes[$key]);
|
||||
} catch (\Illuminate\Contracts\Encryption\DecryptException $e) {
|
||||
} catch (DecryptException $e) {
|
||||
Log::warning('Failed to decrypt array object', ['key' => $key, 'error' => $e->getMessage()]);
|
||||
|
||||
return null;
|
||||
|
|
@ -59,7 +61,7 @@ class EncryptArrayObject implements CastsAttributes
|
|||
/**
|
||||
* Prepare the given value for storage.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param Model $model
|
||||
* @param mixed $value
|
||||
* @param array<string, mixed> $attributes
|
||||
* @return array<string, string>|null
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace Core\Front\Admin;
|
|||
use Core\Front\Admin\Contracts\AdminMenuProvider;
|
||||
use Core\Front\Admin\Contracts\DynamicMenuProvider;
|
||||
use Core\Front\Admin\Validation\IconValidator;
|
||||
use Core\Tenant\Services\EntitlementService;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
|
|
@ -111,8 +112,8 @@ class AdminMenuRegistry
|
|||
|
||||
public function __construct(?object $entitlements = null, ?IconValidator $iconValidator = null)
|
||||
{
|
||||
if ($entitlements === null && class_exists(\Core\Tenant\Services\EntitlementService::class)) {
|
||||
$this->entitlements = app(\Core\Tenant\Services\EntitlementService::class);
|
||||
if ($entitlements === null && class_exists(EntitlementService::class)) {
|
||||
$this->entitlements = app(EntitlementService::class);
|
||||
} else {
|
||||
$this->entitlements = $entitlements;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,9 +31,15 @@ use Core\Front\Admin\View\Components\Stats;
|
|||
use Core\Front\Admin\View\Components\StatusCards;
|
||||
use Core\Headers\SecurityHeaders;
|
||||
use Core\LifecycleEventProvider;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
/**
|
||||
* Admin frontage - admin dashboard stage.
|
||||
|
|
@ -49,12 +55,12 @@ class Boot extends ServiceProvider
|
|||
public static function middleware(Middleware $middleware): void
|
||||
{
|
||||
$middleware->group('admin', [
|
||||
\Illuminate\Cookie\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
ValidateCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
SecurityHeaders::class,
|
||||
'auth',
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ActivityFeed extends Component
|
||||
|
|
@ -34,7 +35,7 @@ class ActivityFeed extends Component
|
|||
return $item['color'] ?? 'gray';
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.activity-feed');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Core\Front\Admin\View\Components;
|
|||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Pagination\Paginator;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ActivityLog extends Component
|
||||
|
|
@ -57,7 +58,7 @@ class ActivityLog extends Component
|
|||
return is_array($value) ? json_encode($value) : (string) $value;
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.activity-log');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Alert extends Component
|
||||
|
|
@ -42,7 +43,7 @@ class Alert extends Component
|
|||
};
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.alert');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class CardGrid extends Component
|
||||
|
|
@ -52,7 +53,7 @@ class CardGrid extends Component
|
|||
};
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.card-grid');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ClearFilters extends Component
|
||||
|
|
@ -26,7 +27,7 @@ class ClearFilters extends Component
|
|||
->implode('; ');
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.clear-filters');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class DataTable extends Component
|
||||
|
|
@ -45,7 +46,7 @@ class DataTable extends Component
|
|||
return $align === 'right' ? 'text-right' : '';
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.data-table');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\Pagination\Paginator;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class EditableTable extends Component
|
||||
|
|
@ -74,7 +75,7 @@ class EditableTable extends Component
|
|||
return count($this->columns) + ($this->selectable ? 1 : 0);
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.editable-table');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
|
|
@ -49,7 +50,7 @@ class Filter extends Component
|
|||
})->values()->all();
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.filter');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class FilterBar extends Component
|
||||
|
|
@ -34,7 +35,7 @@ class FilterBar extends Component
|
|||
};
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.filter-bar');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class LinkGrid extends Component
|
||||
|
|
@ -44,7 +45,7 @@ class LinkGrid extends Component
|
|||
return $item['icon'] ?? 'arrow-right';
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.link-grid');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\Pagination\Paginator;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ManagerTable extends Component
|
||||
|
|
@ -56,7 +57,7 @@ class ManagerTable extends Component
|
|||
};
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.manager-table');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Metrics extends Component
|
||||
|
|
@ -34,7 +35,7 @@ class Metrics extends Component
|
|||
};
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.metrics');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ProgressList extends Component
|
||||
|
|
@ -52,7 +53,7 @@ class ProgressList extends Component
|
|||
return is_numeric($value) ? number_format($value) : (string) $value;
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.progress-list');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Search extends Component
|
||||
|
|
@ -24,7 +25,7 @@ class Search extends Component
|
|||
$this->wireModel = $this->model ? "wire:model.live.debounce.300ms=\"{$this->model}\"" : '';
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.search');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ServiceCard extends Component
|
||||
|
|
@ -52,7 +53,7 @@ class ServiceCard extends Component
|
|||
$this->statusColor = $this->status === 'online' ? 'green' : 'red';
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.service-card');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ declare(strict_types=1);
|
|||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Core\Front\Admin\AdminMenuRegistry;
|
||||
use Core\Tenant\Models\User;
|
||||
use Core\Tenant\Services\WorkspaceService;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
|
|
@ -39,19 +42,19 @@ class Sidemenu extends Component
|
|||
|
||||
// Use current workspace from session, not default
|
||||
$workspace = null;
|
||||
if (class_exists(\Core\Tenant\Services\WorkspaceService::class)) {
|
||||
$workspace = app(\Core\Tenant\Services\WorkspaceService::class)->currentModel();
|
||||
if (class_exists(WorkspaceService::class)) {
|
||||
$workspace = app(WorkspaceService::class)->currentModel();
|
||||
}
|
||||
|
||||
$isAdmin = false;
|
||||
if (class_exists(\Core\Tenant\Models\User::class) && $user instanceof \Core\Tenant\Models\User) {
|
||||
if (class_exists(User::class) && $user instanceof User) {
|
||||
$isAdmin = $user->isHades();
|
||||
}
|
||||
|
||||
return app(AdminMenuRegistry::class)->build($workspace, $isAdmin);
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.sidemenu');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Stats extends Component
|
||||
|
|
@ -36,7 +37,7 @@ class Stats extends Component
|
|||
};
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.stats');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Admin\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class StatusCards extends Component
|
||||
|
|
@ -39,7 +40,7 @@ class StatusCards extends Component
|
|||
return $item['color'] ?? 'gray';
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('admin::components.status-cards');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ declare(strict_types=1);
|
|||
namespace Core\Front;
|
||||
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Support\AggregateServiceProvider;
|
||||
|
||||
/**
|
||||
|
|
@ -54,12 +56,12 @@ class Boot extends AggregateServiceProvider
|
|||
// Application::configure(), before package providers load.
|
||||
// Packages add their own aliases during boot via lifecycle events.
|
||||
$middleware->group('api', [
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
ThrottleRequests::class.':api',
|
||||
SubstituteBindings::class,
|
||||
]);
|
||||
$middleware->group('mcp', [
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
ThrottleRequests::class.':api',
|
||||
SubstituteBindings::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Front\Cli;
|
||||
|
||||
use Core\Actions\ScheduleServiceProvider;
|
||||
use Core\Events\ConsoleBooting;
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
|
@ -35,7 +37,7 @@ class Boot extends ServiceProvider
|
|||
return;
|
||||
}
|
||||
|
||||
$this->app->register(\Core\Actions\ScheduleServiceProvider::class);
|
||||
$this->app->register(ScheduleServiceProvider::class);
|
||||
|
||||
$this->fireConsoleBooting();
|
||||
}
|
||||
|
|
@ -58,7 +60,7 @@ class Boot extends ServiceProvider
|
|||
}
|
||||
|
||||
// Process middleware aliases
|
||||
$router = $this->app->make(\Illuminate\Routing\Router::class);
|
||||
$router = $this->app->make(Router::class);
|
||||
foreach ($event->middlewareRequests() as [$alias, $class]) {
|
||||
$router->aliasMiddleware($alias, $class);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Core PHP Framework
|
||||
*
|
||||
|
|
|
|||
|
|
@ -15,9 +15,15 @@ use Core\Front\Web\Middleware\FindDomainRecord;
|
|||
use Core\Front\Web\Middleware\ResilientSession;
|
||||
use Core\Headers\SecurityHeaders;
|
||||
use Core\LifecycleEventProvider;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
/**
|
||||
* Web frontage - public marketing stage.
|
||||
|
|
@ -33,13 +39,13 @@ class Boot extends ServiceProvider
|
|||
public static function middleware(Middleware $middleware): void
|
||||
{
|
||||
$middleware->group('web', [
|
||||
\Illuminate\Cookie\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
ResilientSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
ShareErrorsFromSession::class,
|
||||
ValidateCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
SecurityHeaders::class,
|
||||
FindDomainRecord::class,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||
namespace Core\Front\Web\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Core\Tenant\Models\Workspace;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
|
|
@ -84,12 +85,12 @@ class FindDomainRecord
|
|||
protected function resolveWorkspaceFromDomain(string $host): ?object
|
||||
{
|
||||
// Check if Tenant module is installed
|
||||
if (! class_exists(\Core\Tenant\Models\Workspace::class)) {
|
||||
if (! class_exists(Workspace::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for custom domain first
|
||||
$workspace = \Core\Tenant\Models\Workspace::where('domain', $host)->first();
|
||||
$workspace = Workspace::where('domain', $host)->first();
|
||||
if ($workspace) {
|
||||
return $workspace;
|
||||
}
|
||||
|
|
@ -104,7 +105,7 @@ class FindDomainRecord
|
|||
if (count($parts) >= 1) {
|
||||
$workspaceSlug = $parts[0];
|
||||
|
||||
return \Core\Tenant\Models\Workspace::where('slug', $workspaceSlug)
|
||||
return Workspace::where('slug', $workspaceSlug)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Core\Front\Web\Middleware;
|
|||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
|
@ -43,7 +44,7 @@ class ResilientSession
|
|||
return $next($request);
|
||||
} catch (DecryptException $e) {
|
||||
return $this->handleSessionError($request, $e, 'decrypt');
|
||||
} catch (\Illuminate\Database\QueryException $e) {
|
||||
} catch (QueryException $e) {
|
||||
// Only catch session-related query exceptions
|
||||
if ($this->isSessionError($e)) {
|
||||
return $this->handleSessionError($request, $e, 'database');
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Headers\Livewire;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Livewire\Component;
|
||||
|
||||
|
|
@ -451,7 +452,7 @@ class HeaderConfigurationManager extends Component
|
|||
/**
|
||||
* Render the component.
|
||||
*/
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
return view('core::headers.livewire.header-configuration-manager');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ declare(strict_types=1);
|
|||
namespace Core\Lang\Console\Commands;
|
||||
|
||||
use Core\Lang\Coverage\TranslationCoverage;
|
||||
use Core\Lang\Coverage\TranslationCoverageReport;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
|
||||
/**
|
||||
* Report translation coverage across the codebase.
|
||||
|
|
@ -164,7 +167,7 @@ class TranslationCoverageCommand extends Command
|
|||
/**
|
||||
* Display per-locale statistics.
|
||||
*
|
||||
* @param \Core\Lang\Coverage\TranslationCoverageReport $report
|
||||
* @param TranslationCoverageReport $report
|
||||
*/
|
||||
protected function displayLocaleStats($report): void
|
||||
{
|
||||
|
|
@ -201,7 +204,7 @@ class TranslationCoverageCommand extends Command
|
|||
/**
|
||||
* Display missing keys.
|
||||
*
|
||||
* @param \Core\Lang\Coverage\TranslationCoverageReport $report
|
||||
* @param TranslationCoverageReport $report
|
||||
*/
|
||||
protected function displayMissingKeys($report, bool $verbose): void
|
||||
{
|
||||
|
|
@ -241,7 +244,7 @@ class TranslationCoverageCommand extends Command
|
|||
/**
|
||||
* Display unused keys.
|
||||
*
|
||||
* @param \Core\Lang\Coverage\TranslationCoverageReport $report
|
||||
* @param TranslationCoverageReport $report
|
||||
*/
|
||||
protected function displayUnusedKeys($report, bool $verbose): void
|
||||
{
|
||||
|
|
@ -291,8 +294,8 @@ class TranslationCoverageCommand extends Command
|
|||
* Get shell completion suggestions.
|
||||
*/
|
||||
public function complete(
|
||||
\Symfony\Component\Console\Completion\CompletionInput $input,
|
||||
\Symfony\Component\Console\Completion\CompletionSuggestions $suggestions
|
||||
CompletionInput $input,
|
||||
CompletionSuggestions $suggestions
|
||||
): void {
|
||||
if ($input->mustSuggestOptionValuesFor('locale')) {
|
||||
// Suggest available locales
|
||||
|
|
|
|||
|
|
@ -11,8 +11,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Lang\Console\Commands;
|
||||
|
||||
use Core\Lang\TranslationMemory\FuzzyMatcher;
|
||||
use Core\Lang\TranslationMemory\TranslationMemory;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
|
||||
/**
|
||||
* Translation Memory management command.
|
||||
|
|
@ -388,7 +391,7 @@ class TranslationMemoryCommand extends Command
|
|||
$confidence = $suggestion['confidence'];
|
||||
|
||||
$similarityColor = $similarity >= 0.9 ? 'green' : ($similarity >= 0.75 ? 'yellow' : 'red');
|
||||
$category = \Core\Lang\TranslationMemory\FuzzyMatcher::categorizeSimilarity($similarity);
|
||||
$category = FuzzyMatcher::categorizeSimilarity($similarity);
|
||||
|
||||
$this->line(" <fg={$similarityColor};options=bold>".sprintf('%.0f%%', $similarity * 100).'</> match ('.$category.')');
|
||||
$this->line(" <fg=cyan>Source:</> {$entry->getSource()}");
|
||||
|
|
@ -566,8 +569,8 @@ class TranslationMemoryCommand extends Command
|
|||
* Get shell completion suggestions.
|
||||
*/
|
||||
public function complete(
|
||||
\Symfony\Component\Console\Completion\CompletionInput $input,
|
||||
\Symfony\Component\Console\Completion\CompletionSuggestions $suggestions
|
||||
CompletionInput $input,
|
||||
CompletionSuggestions $suggestions
|
||||
): void {
|
||||
if ($input->mustSuggestArgumentValuesFor('action')) {
|
||||
$suggestions->suggestValues([
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ use Core\Events\McpRoutesRegistering;
|
|||
use Core\Events\McpToolsRegistering;
|
||||
use Core\Events\QueueWorkerBooting;
|
||||
use Core\Events\WebRoutesRegistering;
|
||||
use Core\Front\Mcp\Contracts\McpToolHandler;
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Livewire\Livewire;
|
||||
|
|
@ -252,7 +254,7 @@ class LifecycleEventProvider extends ServiceProvider
|
|||
*/
|
||||
protected static function processMiddleware(Events\LifecycleEvent $event): void
|
||||
{
|
||||
/** @var \Illuminate\Routing\Router $router */
|
||||
/** @var Router $router */
|
||||
$router = app('router');
|
||||
|
||||
foreach ($event->middlewareRequests() as [$alias, $class]) {
|
||||
|
|
@ -484,7 +486,7 @@ class LifecycleEventProvider extends ServiceProvider
|
|||
*
|
||||
* @return array<string> Fully qualified class names of McpToolHandler implementations
|
||||
*
|
||||
* @see \Core\Front\Mcp\Contracts\McpToolHandler (in php-mcp package)
|
||||
* @see McpToolHandler (in php-mcp package)
|
||||
*/
|
||||
public static function fireMcpTools(): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use Core\Media\Events\ConversionProgress;
|
|||
use Core\Media\Jobs\ProcessMediaConversion;
|
||||
use Core\Media\Support\ConversionProgressReporter;
|
||||
use Core\Media\Support\MediaConversionData;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
|
|
@ -463,7 +464,7 @@ abstract class MediaConversion
|
|||
/**
|
||||
* Get a filesystem instance for the given disk.
|
||||
*/
|
||||
protected function filesystem(string $disk): \Illuminate\Contracts\Filesystem\Filesystem
|
||||
protected function filesystem(string $disk): Filesystem
|
||||
{
|
||||
return Storage::disk($disk);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* Image optimisation record.
|
||||
|
|
@ -30,8 +31,8 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
|||
* @property int|null $workspace_id
|
||||
* @property string|null $optimizable_type
|
||||
* @property int|null $optimizable_id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
*/
|
||||
class ImageOptimization extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Search\Analytics;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
|
@ -210,9 +211,9 @@ class SearchAnalytics
|
|||
*
|
||||
* @param int $limit Maximum number of queries to return
|
||||
* @param int $days Number of days to look back
|
||||
* @return \Illuminate\Support\Collection<int, object>
|
||||
* @return Collection<int, object>
|
||||
*/
|
||||
public function getPopularQueries(int $limit = 10, int $days = 7): \Illuminate\Support\Collection
|
||||
public function getPopularQueries(int $limit = 10, int $days = 7): Collection
|
||||
{
|
||||
if (! $this->isEnabled()) {
|
||||
return collect();
|
||||
|
|
@ -232,9 +233,9 @@ class SearchAnalytics
|
|||
*
|
||||
* @param int $limit Maximum number of queries to return
|
||||
* @param int $days Number of days to look back
|
||||
* @return \Illuminate\Support\Collection<int, object>
|
||||
* @return Collection<int, object>
|
||||
*/
|
||||
public function getZeroResultQueries(int $limit = 20, int $days = 30): \Illuminate\Support\Collection
|
||||
public function getZeroResultQueries(int $limit = 20, int $days = 30): Collection
|
||||
{
|
||||
if (! $this->isEnabled()) {
|
||||
return collect();
|
||||
|
|
@ -254,9 +255,9 @@ class SearchAnalytics
|
|||
* Get search trend over time.
|
||||
*
|
||||
* @param int $days Number of days to look back
|
||||
* @return \Illuminate\Support\Collection<int, object>
|
||||
* @return Collection<int, object>
|
||||
*/
|
||||
public function getTrend(int $days = 30): \Illuminate\Support\Collection
|
||||
public function getTrend(int $days = 30): Collection
|
||||
{
|
||||
if (! $this->isEnabled()) {
|
||||
return collect();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Search;
|
||||
|
||||
use App\Services\Search\UnifiedSearchService;
|
||||
use Core\Search\Analytics\SearchAnalytics;
|
||||
use Core\Search\Suggestions\SearchSuggestions;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
|
@ -72,8 +73,8 @@ class Boot extends ServiceProvider
|
|||
*/
|
||||
protected function registerBackwardCompatAliases(): void
|
||||
{
|
||||
if (! class_exists(\App\Services\Search\UnifiedSearchService::class)) {
|
||||
class_alias(Unified::class, \App\Services\Search\UnifiedSearchService::class);
|
||||
if (! class_exists(UnifiedSearchService::class)) {
|
||||
class_alias(Unified::class, UnifiedSearchService::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Search\Suggestions;
|
||||
|
||||
use Core\Mod\Uptelligence\Models\Asset;
|
||||
use Core\Mod\Uptelligence\Models\Pattern;
|
||||
use Core\Search\Analytics\SearchAnalytics;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
|
@ -43,7 +46,7 @@ use Illuminate\Support\Facades\Schema;
|
|||
* 'sources' => ['popular', 'recent', 'content'],
|
||||
* ]
|
||||
*
|
||||
* @see \Core\Search\Analytics\SearchAnalytics For search tracking integration
|
||||
* @see SearchAnalytics For search tracking integration
|
||||
*/
|
||||
class SearchSuggestions
|
||||
{
|
||||
|
|
@ -259,9 +262,9 @@ class SearchSuggestions
|
|||
$escaped = $this->escapeLikeQuery($prefix);
|
||||
|
||||
// Search patterns if available
|
||||
if (class_exists(\Core\Mod\Uptelligence\Models\Pattern::class)) {
|
||||
if (class_exists(Pattern::class)) {
|
||||
try {
|
||||
$patterns = \Core\Mod\Uptelligence\Models\Pattern::where('name', 'like', "{$escaped}%")
|
||||
$patterns = Pattern::where('name', 'like', "{$escaped}%")
|
||||
->limit($limit)
|
||||
->pluck('name')
|
||||
->map(fn ($name) => [
|
||||
|
|
@ -278,9 +281,9 @@ class SearchSuggestions
|
|||
}
|
||||
|
||||
// Search assets if available
|
||||
if (class_exists(\Core\Mod\Uptelligence\Models\Asset::class)) {
|
||||
if (class_exists(Asset::class)) {
|
||||
try {
|
||||
$assets = \Core\Mod\Uptelligence\Models\Asset::where('name', 'like', "{$escaped}%")
|
||||
$assets = Asset::where('name', 'like', "{$escaped}%")
|
||||
->limit($limit)
|
||||
->pluck('name')
|
||||
->map(fn ($name) => [
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Search;
|
||||
|
||||
use Core\Mod\Agentic\Models\AgentPlan;
|
||||
use Core\Mod\Uptelligence\Models\Asset;
|
||||
use Core\Mod\Uptelligence\Models\Pattern;
|
||||
use Core\Mod\Uptelligence\Models\UpstreamTodo;
|
||||
use Core\Search\Analytics\SearchAnalytics;
|
||||
use Core\Search\Suggestions\SearchSuggestions;
|
||||
use Illuminate\Support\Collection;
|
||||
|
|
@ -410,14 +414,14 @@ class Unified
|
|||
*/
|
||||
protected function searchPatterns(string $query): Collection
|
||||
{
|
||||
if (! class_exists(\Core\Mod\Uptelligence\Models\Pattern::class)) {
|
||||
if (! class_exists(Pattern::class)) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$escaped = $this->escapeLikeQuery($query);
|
||||
|
||||
try {
|
||||
return \Core\Mod\Uptelligence\Models\Pattern::where('name', 'like', "%{$escaped}%")
|
||||
return Pattern::where('name', 'like', "%{$escaped}%")
|
||||
->orWhere('description', 'like', "%{$escaped}%")
|
||||
->orWhere('category', 'like', "%{$escaped}%")
|
||||
->limit(20)
|
||||
|
|
@ -448,14 +452,14 @@ class Unified
|
|||
*/
|
||||
protected function searchAssets(string $query): Collection
|
||||
{
|
||||
if (! class_exists(\Core\Mod\Uptelligence\Models\Asset::class)) {
|
||||
if (! class_exists(Asset::class)) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$escaped = $this->escapeLikeQuery($query);
|
||||
|
||||
try {
|
||||
return \Core\Mod\Uptelligence\Models\Asset::where('name', 'like', "%{$escaped}%")
|
||||
return Asset::where('name', 'like', "%{$escaped}%")
|
||||
->orWhere('slug', 'like', "%{$escaped}%")
|
||||
->orWhere('description', 'like', "%{$escaped}%")
|
||||
->limit(20)
|
||||
|
|
@ -487,14 +491,14 @@ class Unified
|
|||
*/
|
||||
protected function searchTodos(string $query): Collection
|
||||
{
|
||||
if (! class_exists(\Core\Mod\Uptelligence\Models\UpstreamTodo::class)) {
|
||||
if (! class_exists(UpstreamTodo::class)) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$escaped = $this->escapeLikeQuery($query);
|
||||
|
||||
try {
|
||||
return \Core\Mod\Uptelligence\Models\UpstreamTodo::where('title', 'like', "%{$escaped}%")
|
||||
return UpstreamTodo::where('title', 'like', "%{$escaped}%")
|
||||
->orWhere('description', 'like', "%{$escaped}%")
|
||||
->limit(20)
|
||||
->get()
|
||||
|
|
@ -525,14 +529,14 @@ class Unified
|
|||
*/
|
||||
protected function searchPlans(string $query): Collection
|
||||
{
|
||||
if (! class_exists(\Core\Mod\Agentic\Models\AgentPlan::class)) {
|
||||
if (! class_exists(AgentPlan::class)) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$escaped = $this->escapeLikeQuery($query);
|
||||
|
||||
try {
|
||||
return \Core\Mod\Agentic\Models\AgentPlan::where('title', 'like', "%{$escaped}%")
|
||||
return AgentPlan::where('title', 'like', "%{$escaped}%")
|
||||
->orWhere('slug', 'like', "%{$escaped}%")
|
||||
->orWhere('description', 'like', "%{$escaped}%")
|
||||
->limit(20)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Seo;
|
||||
|
||||
use App\Services\Content\SchemaService;
|
||||
use Core\Seo\Analytics\SeoScoreTrend;
|
||||
use Core\Seo\Console\Commands\AuditCanonicalUrls;
|
||||
use Core\Seo\Console\Commands\RecordSeoScores;
|
||||
|
|
@ -105,8 +106,8 @@ class Boot extends ServiceProvider
|
|||
protected function registerBackwardCompatAliases(): void
|
||||
{
|
||||
// Schema (high-level JSON-LD generator)
|
||||
if (! class_exists(\App\Services\Content\SchemaService::class)) {
|
||||
class_alias(Schema::class, \App\Services\Content\SchemaService::class);
|
||||
if (! class_exists(SchemaService::class)) {
|
||||
class_alias(Schema::class, SchemaService::class);
|
||||
}
|
||||
|
||||
// Services
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Core PHP Framework
|
||||
*
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Seo\Jobs;
|
||||
|
||||
use Core\Mod\Web\Models\Page;
|
||||
use Core\Mod\Web\Services\DynamicOgImageService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
|
@ -64,20 +66,20 @@ class GenerateOgImageJob implements ShouldQueue
|
|||
public function handle(): void
|
||||
{
|
||||
// Check if required Web module classes exist
|
||||
if (! class_exists(\Core\Mod\Web\Models\Page::class)) {
|
||||
if (! class_exists(Page::class)) {
|
||||
Log::warning('OG image generation skipped: Web module not installed');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! class_exists(\Core\Mod\Web\Services\DynamicOgImageService::class)) {
|
||||
if (! class_exists(DynamicOgImageService::class)) {
|
||||
Log::warning('OG image generation skipped: DynamicOgImageService not available');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ogService = app(\Core\Mod\Web\Services\DynamicOgImageService::class);
|
||||
$page = \Core\Mod\Web\Models\Page::find($this->pageId);
|
||||
$ogService = app(DynamicOgImageService::class);
|
||||
$page = Page::find($this->pageId);
|
||||
|
||||
if (! $page) {
|
||||
Log::warning("OG image generation skipped: page {$this->pageId} not found");
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Core\Seo\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Core\Seo\SeoMetadata;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
|
@ -42,8 +44,8 @@ use Illuminate\Support\Collection;
|
|||
* @property array<string>|null $issues
|
||||
* @property array<string>|null $suggestions
|
||||
* @property array<string, mixed>|null $snapshot
|
||||
* @property \Carbon\Carbon $recorded_at
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property Carbon $recorded_at
|
||||
* @property Carbon $created_at
|
||||
*/
|
||||
class SeoScoreHistory extends Model
|
||||
{
|
||||
|
|
@ -100,7 +102,7 @@ class SeoScoreHistory extends Model
|
|||
*/
|
||||
public function seoMetadata(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\Core\Seo\SeoMetadata::class, 'seo_metadata_id');
|
||||
return $this->belongsTo(SeoMetadata::class, 'seo_metadata_id');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -224,11 +226,11 @@ class SeoScoreHistory extends Model
|
|||
/**
|
||||
* Get scores recorded within a date range.
|
||||
*
|
||||
* @param \Carbon\Carbon $from Start date
|
||||
* @param \Carbon\Carbon $to End date
|
||||
* @param Carbon $from Start date
|
||||
* @param Carbon $to End date
|
||||
* @return Collection<int, static>
|
||||
*/
|
||||
public static function inDateRange(\Carbon\Carbon $from, \Carbon\Carbon $to): Collection
|
||||
public static function inDateRange(Carbon $from, Carbon $to): Collection
|
||||
{
|
||||
return static::whereBetween('recorded_at', [$from, $to])
|
||||
->orderByDesc('recorded_at')
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ namespace Core\Seo;
|
|||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* SEO metadata for any model.
|
||||
|
|
@ -31,8 +33,8 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
|||
* @property int|null $seo_score
|
||||
* @property array<string>|null $seo_issues
|
||||
* @property array<string>|null $seo_suggestions
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
*/
|
||||
class SeoMetadata extends Model
|
||||
{
|
||||
|
|
@ -317,7 +319,7 @@ class SeoMetadata extends Model
|
|||
/**
|
||||
* Check if this canonical URL conflicts with other records.
|
||||
*
|
||||
* @return array{has_conflict: bool, count: int, records: \Illuminate\Support\Collection}
|
||||
* @return array{has_conflict: bool, count: int, records: Collection}
|
||||
*/
|
||||
public function checkCanonicalConflict(): array
|
||||
{
|
||||
|
|
@ -370,9 +372,9 @@ class SeoMetadata extends Model
|
|||
* Get score history for this metadata.
|
||||
*
|
||||
* @param int $limit Maximum records to return
|
||||
* @return \Illuminate\Support\Collection<int, Models\SeoScoreHistory>
|
||||
* @return Collection<int, Models\SeoScoreHistory>
|
||||
*/
|
||||
public function getScoreHistory(int $limit = 100): \Illuminate\Support\Collection
|
||||
public function getScoreHistory(int $limit = 100): Collection
|
||||
{
|
||||
$trend = app(Analytics\SeoScoreTrend::class);
|
||||
|
||||
|
|
@ -383,9 +385,9 @@ class SeoMetadata extends Model
|
|||
* Get daily score trend for this metadata.
|
||||
*
|
||||
* @param int $days Days to look back
|
||||
* @return \Illuminate\Support\Collection<int, object>
|
||||
* @return Collection<int, object>
|
||||
*/
|
||||
public function getDailyScoreTrend(int $days = 30): \Illuminate\Support\Collection
|
||||
public function getDailyScoreTrend(int $days = 30): Collection
|
||||
{
|
||||
$trend = app(Analytics\SeoScoreTrend::class);
|
||||
|
||||
|
|
@ -396,9 +398,9 @@ class SeoMetadata extends Model
|
|||
* Get weekly score trend for this metadata.
|
||||
*
|
||||
* @param int $weeks Weeks to look back
|
||||
* @return \Illuminate\Support\Collection<int, object>
|
||||
* @return Collection<int, object>
|
||||
*/
|
||||
public function getWeeklyScoreTrend(int $weeks = 12): \Illuminate\Support\Collection
|
||||
public function getWeeklyScoreTrend(int $weeks = 12): Collection
|
||||
{
|
||||
$trend = app(Analytics\SeoScoreTrend::class);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use Illuminate\Contracts\Events\Dispatcher;
|
|||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Predis\Client;
|
||||
|
||||
/**
|
||||
* Provides resilient cache/session configuration.
|
||||
|
|
@ -178,7 +179,7 @@ class CacheResilienceProvider extends ServiceProvider
|
|||
}
|
||||
|
||||
// Fall back to Predis library
|
||||
if (class_exists(\Predis\Client::class)) {
|
||||
if (class_exists(Client::class)) {
|
||||
return $this->checkPredis($host, $port, $password, $timeout);
|
||||
}
|
||||
|
||||
|
|
@ -233,7 +234,7 @@ class CacheResilienceProvider extends ServiceProvider
|
|||
$options['password'] = $password;
|
||||
}
|
||||
|
||||
$client = new \Predis\Client($options, [
|
||||
$client = new Client($options, [
|
||||
'exceptions' => true,
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ namespace Core\Storage\Commands;
|
|||
|
||||
use Core\Storage\CacheWarmer;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
|
||||
/**
|
||||
* Warm registered cache items.
|
||||
|
|
@ -271,8 +273,8 @@ class WarmCacheCommand extends Command
|
|||
* Get shell completion suggestions for options.
|
||||
*/
|
||||
public function complete(
|
||||
\Symfony\Component\Console\Completion\CompletionInput $input,
|
||||
\Symfony\Component\Console\Completion\CompletionSuggestions $suggestions
|
||||
CompletionInput $input,
|
||||
CompletionSuggestions $suggestions
|
||||
): void {
|
||||
if ($input->mustSuggestOptionValuesFor('store')) {
|
||||
// Suggest common cache stores
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ declare(strict_types=1);
|
|||
*/
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\View\Compilers\BladeCompiler;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
uses()->group('admin-components');
|
||||
|
|
@ -307,7 +308,7 @@ describe('Admin Component Integration', function () {
|
|||
$errors = [];
|
||||
foreach ($files as $file) {
|
||||
try {
|
||||
$compiler = app(\Illuminate\View\Compilers\BladeCompiler::class);
|
||||
$compiler = app(BladeCompiler::class);
|
||||
$compiler->compile($file);
|
||||
} catch (Throwable $e) {
|
||||
$errors[] = [
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ describe('Admin Route Discovery', function () {
|
|||
|
||||
try {
|
||||
$response = $this->actingAs($this->hadesUser)->get('/'.$uri);
|
||||
} catch (\Throwable $e) {
|
||||
} catch (Throwable $e) {
|
||||
$failures[] = [
|
||||
'uri' => $uri,
|
||||
'status' => 500,
|
||||
|
|
@ -338,7 +338,7 @@ describe('Hub Route Architecture', function () {
|
|||
* Uses middleware detection to find admin routes, not URL patterns.
|
||||
* This catches all routes with 'admin' middleware regardless of URL structure.
|
||||
*
|
||||
* @return array<\Illuminate\Routing\Route>
|
||||
* @return array<Illuminate\Routing\Route>
|
||||
*/
|
||||
function discoverAdminRoutes(): array
|
||||
{
|
||||
|
|
@ -383,7 +383,7 @@ function discoverAdminRoutes(): array
|
|||
/**
|
||||
* Discover all hub GET routes (hub/*) from Laravel's route registrar.
|
||||
*
|
||||
* @return array<\Illuminate\Routing\Route>
|
||||
* @return array<Illuminate\Routing\Route>
|
||||
*/
|
||||
function discoverHubRoutes(): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Core PHP Framework
|
||||
*
|
||||
|
|
@ -12,10 +14,12 @@ namespace Core\Tests\Feature;
|
|||
use Core\Cdn\Services\AssetPipeline;
|
||||
use Core\Cdn\Services\CdnUrlBuilder;
|
||||
use Core\Cdn\Services\StorageUrlResolver;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
|
|
@ -27,7 +31,7 @@ use Tests\TestCase;
|
|||
* - CDN delivery
|
||||
* - vBucket isolation
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\Group('slow')]
|
||||
#[Group('slow')]
|
||||
class CdnIntegrationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
|
@ -309,14 +313,14 @@ class CdnIntegrationTest extends TestCase
|
|||
{
|
||||
$disk = $this->urlResolver->publicDisk();
|
||||
|
||||
$this->assertInstanceOf(\Illuminate\Contracts\Filesystem\Filesystem::class, $disk);
|
||||
$this->assertInstanceOf(Filesystem::class, $disk);
|
||||
}
|
||||
|
||||
public function test_url_resolver_returns_private_disk_instance(): void
|
||||
{
|
||||
$disk = $this->urlResolver->privateDisk();
|
||||
|
||||
$this->assertInstanceOf(\Illuminate\Contracts\Filesystem\Filesystem::class, $disk);
|
||||
$this->assertInstanceOf(Filesystem::class, $disk);
|
||||
}
|
||||
|
||||
public function test_asset_pipeline_handles_large_files(): void
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use Core\Config\Models\Channel;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
describe('Channel', function () {
|
||||
describe('inheritanceChain', function () {
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ use Core\Config\Models\ConfigKey;
|
|||
use Core\Config\Models\ConfigProfile;
|
||||
use Core\Config\Models\ConfigResolved;
|
||||
use Core\Config\Models\ConfigValue;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
// Create essential test fixtures
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@ declare(strict_types=1);
|
|||
* Run with: php artisan test app/Core/Tests/Feature/CoreComponentsTest.php
|
||||
*/
|
||||
|
||||
use Core\Front\Components\Button;
|
||||
use Core\Front\Components\Card;
|
||||
use Core\Front\Components\Heading;
|
||||
use Core\Front\Components\Layout;
|
||||
use Core\Front\Components\NavList;
|
||||
use Core\Front\Components\Text;
|
||||
use Core\Pro;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\View\Compilers\BladeCompiler;
|
||||
|
|
@ -28,56 +35,56 @@ describe('Core Detection Helpers', function () {
|
|||
|
||||
it('detects Flux Pro installation', function () {
|
||||
// Flux Pro is installed in this project
|
||||
expect(\Core\Pro::hasFluxPro())->toBeTrue();
|
||||
expect(Pro::hasFluxPro())->toBeTrue();
|
||||
});
|
||||
|
||||
it('identifies Flux Pro components', function () {
|
||||
expect(\Core\Pro::requiresFluxPro('calendar'))->toBeTrue();
|
||||
expect(\Core\Pro::requiresFluxPro('editor'))->toBeTrue();
|
||||
expect(\Core\Pro::requiresFluxPro('chart'))->toBeTrue();
|
||||
expect(\Core\Pro::requiresFluxPro('chart.line'))->toBeTrue(); // Nested component
|
||||
expect(\Core\Pro::requiresFluxPro('core:kanban'))->toBeTrue(); // With prefix
|
||||
expect(Pro::requiresFluxPro('calendar'))->toBeTrue();
|
||||
expect(Pro::requiresFluxPro('editor'))->toBeTrue();
|
||||
expect(Pro::requiresFluxPro('chart'))->toBeTrue();
|
||||
expect(Pro::requiresFluxPro('chart.line'))->toBeTrue(); // Nested component
|
||||
expect(Pro::requiresFluxPro('core:kanban'))->toBeTrue(); // With prefix
|
||||
|
||||
// Free components
|
||||
expect(\Core\Pro::requiresFluxPro('button'))->toBeFalse();
|
||||
expect(\Core\Pro::requiresFluxPro('input'))->toBeFalse();
|
||||
expect(\Core\Pro::requiresFluxPro('modal'))->toBeFalse();
|
||||
expect(Pro::requiresFluxPro('button'))->toBeFalse();
|
||||
expect(Pro::requiresFluxPro('input'))->toBeFalse();
|
||||
expect(Pro::requiresFluxPro('modal'))->toBeFalse();
|
||||
});
|
||||
|
||||
it('respects FontAwesome Pro config', function () {
|
||||
\Core\Pro::clearCache();
|
||||
Pro::clearCache();
|
||||
config(['core.fontawesome.pro' => false]);
|
||||
expect(\Core\Pro::hasFontAwesomePro())->toBeFalse();
|
||||
expect(Pro::hasFontAwesomePro())->toBeFalse();
|
||||
|
||||
\Core\Pro::clearCache();
|
||||
Pro::clearCache();
|
||||
config(['core.fontawesome.pro' => true]);
|
||||
expect(\Core\Pro::hasFontAwesomePro())->toBeTrue();
|
||||
expect(Pro::hasFontAwesomePro())->toBeTrue();
|
||||
|
||||
// Clean up
|
||||
\Core\Pro::clearCache();
|
||||
Pro::clearCache();
|
||||
});
|
||||
|
||||
it('provides correct FA styles based on Pro/Free', function () {
|
||||
\Core\Pro::clearCache();
|
||||
Pro::clearCache();
|
||||
config(['core.fontawesome.pro' => false]);
|
||||
|
||||
$freeStyles = \Core\Pro::fontAwesomeStyles();
|
||||
$freeStyles = Pro::fontAwesomeStyles();
|
||||
expect($freeStyles)->toContain('solid');
|
||||
expect($freeStyles)->toContain('regular');
|
||||
expect($freeStyles)->toContain('brands');
|
||||
expect($freeStyles)->not->toContain('jelly');
|
||||
expect($freeStyles)->not->toContain('light');
|
||||
|
||||
\Core\Pro::clearCache();
|
||||
Pro::clearCache();
|
||||
config(['core.fontawesome.pro' => true]);
|
||||
|
||||
$proStyles = \Core\Pro::fontAwesomeStyles();
|
||||
$proStyles = Pro::fontAwesomeStyles();
|
||||
expect($proStyles)->toContain('jelly');
|
||||
expect($proStyles)->toContain('light');
|
||||
expect($proStyles)->toContain('thin');
|
||||
|
||||
// Clean up
|
||||
\Core\Pro::clearCache();
|
||||
Pro::clearCache();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -670,7 +677,7 @@ describe('Core Pro Component Rendering', function () {
|
|||
describe('Core PHP Builders', function () {
|
||||
|
||||
it('Button builder renders HTML', function () {
|
||||
$button = \Core\Front\Components\Button::make()
|
||||
$button = Button::make()
|
||||
->label('Save')
|
||||
->primary(); // Use the actual API method
|
||||
|
||||
|
|
@ -682,7 +689,7 @@ describe('Core PHP Builders', function () {
|
|||
});
|
||||
|
||||
it('Card builder renders with title and body', function () {
|
||||
$card = \Core\Front\Components\Card::make()
|
||||
$card = Card::make()
|
||||
->title('Card Title')
|
||||
->body('Card content goes here');
|
||||
|
||||
|
|
@ -694,7 +701,7 @@ describe('Core PHP Builders', function () {
|
|||
});
|
||||
|
||||
it('Heading builder renders with level', function () {
|
||||
$heading = \Core\Front\Components\Heading::make()
|
||||
$heading = Heading::make()
|
||||
->text('Page Title')
|
||||
->level(1);
|
||||
|
||||
|
|
@ -706,7 +713,7 @@ describe('Core PHP Builders', function () {
|
|||
});
|
||||
|
||||
it('Text builder renders with variant', function () {
|
||||
$text = \Core\Front\Components\Text::make()
|
||||
$text = Text::make()
|
||||
->content('Some text')
|
||||
->muted();
|
||||
|
||||
|
|
@ -717,7 +724,7 @@ describe('Core PHP Builders', function () {
|
|||
|
||||
it('NavList builder renders items', function () {
|
||||
// NavList::item() signature is: label, href, active, icon
|
||||
$navlist = \Core\Front\Components\NavList::make()
|
||||
$navlist = NavList::make()
|
||||
->item('Dashboard', '/dashboard', false, 'home')
|
||||
->item('Settings', '/settings', false, 'cog');
|
||||
|
||||
|
|
@ -732,7 +739,7 @@ describe('Core PHP Builders', function () {
|
|||
|
||||
it('Layout builder renders HLCRF structure', function () {
|
||||
// Layout uses short method names: h(), c(), f()
|
||||
$layout = \Core\Front\Components\Layout::make('HCF')
|
||||
$layout = Layout::make('HCF')
|
||||
->h('Header Content')
|
||||
->c('Main Content')
|
||||
->f('Footer Content');
|
||||
|
|
@ -810,7 +817,7 @@ describe('Custom Components (Non-Flux)', function () {
|
|||
|
||||
it('core:icon falls back to solid for jelly when FA Free', function () {
|
||||
// Without FA Pro config, jelly icons should fall back to solid
|
||||
\Core\Pro::clearCache();
|
||||
Pro::clearCache();
|
||||
config(['core.fontawesome.pro' => false]);
|
||||
|
||||
$html = Blade::render('<core:icon name="globe" />');
|
||||
|
|
@ -820,7 +827,7 @@ describe('Custom Components (Non-Flux)', function () {
|
|||
|
||||
it('core:icon uses jelly style when FA Pro enabled', function () {
|
||||
// With FA Pro config, jelly icons should use fa-jelly
|
||||
\Core\Pro::clearCache();
|
||||
Pro::clearCache();
|
||||
config(['core.fontawesome.pro' => true]);
|
||||
|
||||
$html = Blade::render('<core:icon name="globe" />');
|
||||
|
|
@ -828,7 +835,7 @@ describe('Custom Components (Non-Flux)', function () {
|
|||
expect($html)->toContain('fa-jelly');
|
||||
|
||||
// Clean up
|
||||
\Core\Pro::clearCache();
|
||||
Pro::clearCache();
|
||||
config(['core.fontawesome.pro' => false]);
|
||||
});
|
||||
|
||||
|
|
@ -841,7 +848,7 @@ describe('Custom Components (Non-Flux)', function () {
|
|||
});
|
||||
|
||||
it('core:icon falls back pro-only styles to free equivalents', function () {
|
||||
\Core\Pro::clearCache();
|
||||
Pro::clearCache();
|
||||
config(['core.fontawesome.pro' => false]);
|
||||
|
||||
// Light → Regular
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@
|
|||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
use Core\Mod\Commerce\Database\Seeders\TaxRateSeeder;
|
||||
use Core\Tenant\Database\Seeders\PackageSeeder;
|
||||
use Database\Seeders\DatabaseSeeder;
|
||||
|
||||
/**
|
||||
* Database Migration Tests
|
||||
|
|
@ -84,15 +87,15 @@ describe('Database Migrations', function () {
|
|||
});
|
||||
|
||||
it('DatabaseSeeder class exists and has run method', function () {
|
||||
expect(class_exists(\Database\Seeders\DatabaseSeeder::class))->toBeTrue();
|
||||
expect(method_exists(\Database\Seeders\DatabaseSeeder::class, 'run'))->toBeTrue();
|
||||
expect(class_exists(DatabaseSeeder::class))->toBeTrue();
|
||||
expect(method_exists(DatabaseSeeder::class, 'run'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('critical seeder classes exist in modules', function () {
|
||||
// Check for module seeders - these live in Mod namespaces
|
||||
$moduleSeederClasses = [
|
||||
\Core\Tenant\Database\Seeders\PackageSeeder::class,
|
||||
\Core\Mod\Commerce\Database\Seeders\TaxRateSeeder::class,
|
||||
PackageSeeder::class,
|
||||
TaxRateSeeder::class,
|
||||
];
|
||||
|
||||
$found = 0;
|
||||
|
|
|
|||
|
|
@ -14,10 +14,11 @@ use Core\Mail\EmailShield;
|
|||
use Core\Mail\EmailShieldStat;
|
||||
use Core\Mail\EmailValidationResult;
|
||||
use Core\Mail\Rules\ValidatedEmail;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
// Clear the cache before each test to ensure clean state
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ declare(strict_types=1);
|
|||
* Error handling is configured in bootstrap/app.php.
|
||||
*/
|
||||
|
||||
use App\Support\HadesEncrypt;
|
||||
use Core\Tenant\Models\User;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
describe('Static Error Files', function () {
|
||||
|
|
@ -87,7 +89,7 @@ describe('404 Error Page', function () {
|
|||
describe('403 Error Page', function () {
|
||||
it('returns 403 for forbidden access', function () {
|
||||
// Create a regular user (not hades)
|
||||
$user = \Core\Tenant\Models\User::factory()->create([
|
||||
$user = User::factory()->create([
|
||||
'account_type' => 'apollo',
|
||||
]);
|
||||
|
||||
|
|
@ -152,11 +154,11 @@ describe('Error Page Brand Consistency', function () {
|
|||
|
||||
describe('HADES Encryption', function () {
|
||||
it('HadesEncrypt class exists and is functional', function () {
|
||||
expect(class_exists(\App\Support\HadesEncrypt::class))->toBeTrue();
|
||||
expect(class_exists(HadesEncrypt::class))->toBeTrue();
|
||||
|
||||
// Test encryption with a sample exception
|
||||
$exception = new \Exception('Test error message');
|
||||
$encrypted = \App\Support\HadesEncrypt::encrypt($exception);
|
||||
$exception = new Exception('Test error message');
|
||||
$encrypted = HadesEncrypt::encrypt($exception);
|
||||
|
||||
// Should return encrypted string or null if no public key
|
||||
expect($encrypted === null || is_string($encrypted))->toBeTrue();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Core PHP Framework
|
||||
*
|
||||
|
|
@ -110,7 +112,7 @@ describe('ImageOptimizer service', function () {
|
|||
$optimizer = new ImageOptimizer;
|
||||
|
||||
$optimizer->optimize('/path/to/nonexistent/file.jpg');
|
||||
})->throws(\InvalidArgumentException::class, 'File not found');
|
||||
})->throws(InvalidArgumentException::class, 'File not found');
|
||||
|
||||
it('handles invalid image files gracefully', function () {
|
||||
// Create a text file pretending to be an image
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\SendQueuedMailable;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
|
@ -68,12 +70,12 @@ describe('Mail Sending', function () {
|
|||
|
||||
Mail::to('recipient@example.com')->queue($mailable);
|
||||
|
||||
Queue::assertPushed(\Illuminate\Mail\SendQueuedMailable::class);
|
||||
Queue::assertPushed(SendQueuedMailable::class);
|
||||
});
|
||||
|
||||
it('ContactFormSubmission implements ShouldQueue', function () {
|
||||
expect(ContactFormSubmission::class)
|
||||
->toImplement(\Illuminate\Contracts\Queue\ShouldQueue::class);
|
||||
->toImplement(ShouldQueue::class);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use Core\Events\AdminPanelBooting;
|
||||
use Core\Events\WebRoutesRegistering;
|
||||
use Core\LazyModuleListener;
|
||||
use Core\ModuleScanner;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
|
@ -27,8 +28,8 @@ describe('ModuleScanner integration', function () {
|
|||
|
||||
// Common events should have listeners
|
||||
$commonEvents = [
|
||||
\Core\Events\AdminPanelBooting::class,
|
||||
\Core\Events\WebRoutesRegistering::class,
|
||||
AdminPanelBooting::class,
|
||||
WebRoutesRegistering::class,
|
||||
];
|
||||
|
||||
foreach ($commonEvents as $event) {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue