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:
Snider 2026-03-24 11:35:11 +00:00
commit 8f2590477c
137 changed files with 654 additions and 448 deletions

View file

@ -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
```

View file

@ -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),
],
];

View file

@ -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;

View file

@ -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
{

View file

@ -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
{

View file

@ -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();

View file

@ -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.");

View file

@ -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;

View file

@ -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');
}

View file

@ -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');

View file

@ -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);

View file

@ -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')

View file

@ -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,
];
}

View file

@ -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);
}
}
}

View file

@ -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;
}

View file

@ -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');

View file

@ -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
{

View file

@ -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)) {

View file

@ -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
{

View file

@ -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);

View file

@ -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)

View file

@ -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)
{

View file

@ -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');
}

View file

@ -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());
}
}
}

View file

@ -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());
}
}
}

View file

@ -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}");

View file

@ -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);
});

View file

@ -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());
}
}
}

View file

@ -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
{

View file

@ -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

View file

@ -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();
}

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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')

View file

@ -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

View file

@ -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']);

View file

@ -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']);

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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',
]);

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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,
]);
}
}

View file

@ -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);
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* Core PHP Framework
*

View file

@ -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,
]);

View file

@ -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();
}

View file

@ -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');

View file

@ -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');
}

View file

@ -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

View file

@ -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([

View file

@ -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
{

View file

@ -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);
}

View file

@ -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
{

View file

@ -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();

View file

@ -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);
}
}
}

View file

@ -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) => [

View file

@ -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)

View file

@ -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

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* Core PHP Framework
*

View file

@ -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");

View file

@ -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')

View file

@ -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);

View file

@ -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,
]);

View file

@ -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

View file

@ -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[] = [

View file

@ -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
{

View file

@ -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

View file

@ -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 () {

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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();

View file

@ -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

View file

@ -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);
});
});

View file

@ -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