Enhance ManualServers functionality: add user-provided information display and credential handling; improve TagsInput validation; update navigation properties
This commit is contained in:
@@ -96,19 +96,26 @@ class FilamentInput
|
||||
->disabled($setting->disabled ?? false)
|
||||
->hint($setting->hint ?? '')
|
||||
->hintColor('primary')
|
||||
->helperText($setting->description ?? '');
|
||||
->helperText($setting->description ?? '')
|
||||
->live(onBlur: true);
|
||||
|
||||
// For required TagsInput, validate that array has at least one item
|
||||
// Standard 'required' rule considers empty arrays as valid
|
||||
// For required TagsInput, add custom validation
|
||||
if ($setting->required ?? false) {
|
||||
$input->required()
|
||||
->rule(function () {
|
||||
$input->rules([
|
||||
function () {
|
||||
return function (string $attribute, $value, \Closure $fail) {
|
||||
if (!is_array($value) || empty($value)) {
|
||||
$fail('The :attribute field must have at least 1 items.');
|
||||
// Check if value is an array and has at least one item
|
||||
if (!is_array($value)) {
|
||||
$fail('The :attribute field is required.');
|
||||
return;
|
||||
}
|
||||
if (count($value) === 0) {
|
||||
$fail('The :attribute field is required.');
|
||||
return;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
return $input;
|
||||
|
||||
@@ -12,8 +12,8 @@ use Paymenter\Extensions\Servers\ManualServers\ManualServers;
|
||||
class ManualServersManage extends Page implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'ri-hard-drive-3-line';
|
||||
protected string $view = 'manual-servers::admin.pages.manual-servers-manage';
|
||||
protected static ?string $navigationIcon = 'ri-hard-drive-3-line';
|
||||
protected static string $view = 'manual-servers::admin.pages.manual-servers-manage';
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
protected static ?string $slug = 'manual-servers-manage/{service}';
|
||||
public ?Service $service = null;
|
||||
|
||||
@@ -13,9 +13,9 @@ class ManualServersQueue extends Page implements HasTable
|
||||
{
|
||||
use InteractsWithTable;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'ri-hard-drive-3-line';
|
||||
protected string $view = 'manual-servers::admin.pages.manual-servers-queue';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Extensions';
|
||||
protected static ?string $navigationIcon = 'ri-hard-drive-3-line';
|
||||
protected static string $view = 'manual-servers::admin.pages.manual-servers-queue';
|
||||
protected static ?string $navigationGroup = 'Extensions';
|
||||
protected static ?string $navigationLabel = 'Manual Servers';
|
||||
protected static ?int $navigationSort = 100;
|
||||
|
||||
|
||||
@@ -162,6 +162,70 @@ class ManualServers extends Server
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration options for checkout (user-provided data during purchase)
|
||||
*
|
||||
* @param Product $product
|
||||
* @return array
|
||||
*/
|
||||
public function getCheckoutConfig($product = null): array
|
||||
{
|
||||
if (!$product) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Create a temporary service object to use getCredentialFieldsForService
|
||||
$tempService = new Service(['product_id' => $product->id]);
|
||||
$tempService->setRelation('product', $product);
|
||||
|
||||
$fields = $this->getCredentialFieldsForService($tempService);
|
||||
|
||||
// Convert to checkout config format
|
||||
$checkoutFields = [];
|
||||
foreach ($fields as $field) {
|
||||
$checkoutFields[] = [
|
||||
'name' => $field['name'],
|
||||
'type' => $field['type'],
|
||||
'label' => $field['label'],
|
||||
'required' => true,
|
||||
'placeholder' => 'Enter ' . strtolower($field['label']),
|
||||
];
|
||||
}
|
||||
|
||||
return $checkoutFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration options for admin when managing a service
|
||||
* This allows admins to enter credentials and other service-specific data
|
||||
*
|
||||
* @param Service $service
|
||||
* @return array
|
||||
*/
|
||||
public function getUserConfig(Service $service): array
|
||||
{
|
||||
$fields = $this->getCredentialFieldsForService($service);
|
||||
|
||||
// Get existing credentials
|
||||
$credentialsJson = $service->properties->where('key', 'credentials_data')->first()?->value ?? '[]';
|
||||
$existingCredentials = json_decode($credentialsJson, true) ?? [];
|
||||
|
||||
// Format fields for the admin interface
|
||||
$configFields = [];
|
||||
foreach ($fields as $field) {
|
||||
$configFields[] = [
|
||||
'name' => $field['name'],
|
||||
'type' => $field['type'],
|
||||
'label' => $field['label'],
|
||||
'required' => false,
|
||||
'default' => $existingCredentials[$field['name']] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return $configFields;
|
||||
}
|
||||
|
||||
public function createServer(Service $service, $params = []): bool
|
||||
{
|
||||
$processes = $this->getProcessesForService($service);
|
||||
@@ -181,9 +245,20 @@ class ManualServers extends Server
|
||||
['key' => 'processes_data'],
|
||||
['value' => json_encode($processData)]
|
||||
);
|
||||
|
||||
// Extract credential values from params (checkout form data)
|
||||
$credentialFields = $this->getCredentialFieldsForService($service);
|
||||
$credentials = [];
|
||||
foreach ($credentialFields as $field) {
|
||||
$fieldName = $field['name'];
|
||||
if (isset($params[$fieldName])) {
|
||||
$credentials[$fieldName] = $params[$fieldName];
|
||||
}
|
||||
}
|
||||
|
||||
$service->properties()->updateOrCreate(
|
||||
['key' => 'credentials_data'],
|
||||
['value' => json_encode([])]
|
||||
['value' => json_encode($credentials)]
|
||||
);
|
||||
$service->properties()->updateOrCreate(
|
||||
['key' => 'deployed_at'],
|
||||
@@ -370,13 +445,30 @@ class ManualServers extends Server
|
||||
$credentialsJson = $service->properties->where('key', 'credentials_data')->first()?->value ?? '[]';
|
||||
$deployedAt = $service->properties->where('key', 'deployed_at')->first()?->value;
|
||||
$feedback = $service->properties->where('key', 'feedback')->first()?->value ?? '';
|
||||
|
||||
// Read credentials from individual properties
|
||||
$credentialFields = $this->getCredentialFieldsForService($service);
|
||||
$credentials = [];
|
||||
foreach ($credentialFields as $field) {
|
||||
$fieldName = $field['name'];
|
||||
$value = $service->properties->where('key', $fieldName)->first()?->value;
|
||||
if ($value !== null) {
|
||||
$credentials[$fieldName] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to credentials_data JSON if individual properties are empty
|
||||
if (empty($credentials)) {
|
||||
$credentials = json_decode($credentialsJson, true) ?? [];
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'processes' => json_decode($processesJson, true) ?? [],
|
||||
'credentials' => json_decode($credentialsJson, true) ?? [],
|
||||
'credentials' => $credentials,
|
||||
'deployed_at' => $deployedAt,
|
||||
'feedback' => $feedback,
|
||||
'credential_fields' => $this->getCredentialFieldsForService($service),
|
||||
'credential_fields' => $credentialFields,
|
||||
'buttons' => $this->getButtonsForService($service),
|
||||
'instructions' => $service->product->settings->where('key', 'instructions')->first()?->value ?? '',
|
||||
];
|
||||
|
||||
@@ -65,6 +65,52 @@
|
||||
</div>
|
||||
</x-filament::section>
|
||||
|
||||
@if(!empty($this->credentialData))
|
||||
<x-filament::section>
|
||||
<x-slot name="heading">User-Provided Information</x-slot>
|
||||
<x-slot name="description">
|
||||
Information entered by the client during checkout
|
||||
</x-slot>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@foreach($this->credentialData as $key => $value)
|
||||
@php
|
||||
$fieldConfig = collect($this->credentialFields)->where('name', $key)->first();
|
||||
$label = $fieldConfig['label'] ?? ucfirst(str_replace('_', ' ', $key));
|
||||
$isPassword = ($fieldConfig['type'] ?? 'text') === 'password';
|
||||
@endphp
|
||||
<div class="p-4 rounded-lg border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5">
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">{{ $label }}</p>
|
||||
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
@if($isPassword)
|
||||
<p class="font-mono text-sm" x-data="{ show: false }">
|
||||
<span x-show="!show" class="text-gray-400">••••••••</span>
|
||||
<span x-show="show" class="break-all">{{ $value }}</span>
|
||||
<x-filament::link @click="show = !show" size="xs" class="ml-2">
|
||||
<span x-show="!show">Show</span>
|
||||
<span x-show="show">Hide</span>
|
||||
</x-filament::link>
|
||||
</p>
|
||||
@else
|
||||
<p class="font-mono text-sm break-all">{{ $value ?: 'N/A' }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<x-filament::button
|
||||
size="xs"
|
||||
color="gray"
|
||||
x-data
|
||||
x-on:click="navigator.clipboard.writeText('{{ addslashes($value) }}'); $tooltip('Copied!')">
|
||||
Copy
|
||||
</x-filament::button>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-filament::section>
|
||||
@endif
|
||||
|
||||
@if(empty($this->processData))
|
||||
<x-filament::section>
|
||||
<x-slot name="heading">Initialize Setup Flow</x-slot>
|
||||
|
||||
3
storage/framework/cache/.gitignore
vendored
3
storage/framework/cache/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
*
|
||||
!data/
|
||||
!.gitignore
|
||||
440
vendor/_laravel_ide/discover-cfae44312a91110c691459b4106ded61.php
vendored
Normal file
440
vendor/_laravel_ide/discover-cfae44312a91110c691459b4106ded61.php
vendored
Normal file
@@ -0,0 +1,440 @@
|
||||
<?php
|
||||
|
||||
|
||||
error_reporting(E_ERROR | E_PARSE);
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
require_once __DIR__ . '/../autoload.php';
|
||||
|
||||
class LaravelVsCode
|
||||
{
|
||||
public static function relativePath($path)
|
||||
{
|
||||
if (!str_contains($path, base_path())) {
|
||||
return (string) $path;
|
||||
}
|
||||
|
||||
return ltrim(str_replace(base_path(), '', realpath($path) ?: $path), DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
public static function isVendor($path)
|
||||
{
|
||||
return str_contains($path, base_path("vendor"));
|
||||
}
|
||||
|
||||
public static function outputMarker($key)
|
||||
{
|
||||
return '__VSCODE_LARAVEL_' . $key . '__';
|
||||
}
|
||||
|
||||
public static function startupError(\Throwable $e)
|
||||
{
|
||||
throw new Error(self::outputMarker('STARTUP_ERROR') . ': ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$app = require_once __DIR__ . '/../../bootstrap/app.php';
|
||||
} catch (\Throwable $e) {
|
||||
LaravelVsCode::startupError($e);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$app->register(new class($app) extends \Illuminate\Support\ServiceProvider
|
||||
{
|
||||
public function boot()
|
||||
{
|
||||
config([
|
||||
'logging.channels.null' => [
|
||||
'driver' => 'monolog',
|
||||
'handler' => \Monolog\Handler\NullHandler::class,
|
||||
],
|
||||
'logging.default' => 'null',
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||
$kernel->bootstrap();
|
||||
} catch (\Throwable $e) {
|
||||
LaravelVsCode::startupError($e);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo LaravelVsCode::outputMarker('START_OUTPUT');
|
||||
|
||||
$components = new class {
|
||||
protected $autoloaded = [];
|
||||
|
||||
protected $prefixes = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->autoloaded = require base_path("vendor/composer/autoload_psr4.php");
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
$components = collect(array_merge(
|
||||
$this->getStandardClasses(),
|
||||
$this->getStandardViews(),
|
||||
$this->getNamespaced(),
|
||||
$this->getAnonymousNamespaced(),
|
||||
$this->getAnonymous(),
|
||||
$this->getAliases(),
|
||||
$this->getVendorComponents(),
|
||||
))->groupBy('key')->pipe($this->setProps(...))->map(fn($items) => [
|
||||
'isVendor' => $items->first()['isVendor'],
|
||||
'paths' => $items->pluck('path')->unique()->values(),
|
||||
'props' => $this->formatProps($items),
|
||||
]);
|
||||
|
||||
return [
|
||||
'components' => $components,
|
||||
'prefixes' => $this->prefixes,
|
||||
];
|
||||
}
|
||||
|
||||
protected function formatProps($items)
|
||||
{
|
||||
$props = $items->pluck('props');
|
||||
|
||||
if ($codeBlock = $props->firstWhere(fn ($prop) => is_string($prop))) {
|
||||
return $codeBlock;
|
||||
}
|
||||
|
||||
return $props->values()->filter()->flatMap(fn($i) => $i);
|
||||
}
|
||||
|
||||
protected function getStandardViews()
|
||||
{
|
||||
$path = resource_path('views/components');
|
||||
|
||||
return $this->findFiles($path, 'blade.php');
|
||||
}
|
||||
|
||||
protected function findFiles($path, $extension, $keyCallback = null)
|
||||
{
|
||||
if (!is_dir($path)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = \Symfony\Component\Finder\Finder::create()
|
||||
->files()
|
||||
->name("*." . $extension)
|
||||
->in($path);
|
||||
$components = [];
|
||||
$pathRealPath = realpath($path);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$realPath = $file->getRealPath();
|
||||
|
||||
$key = str($realPath)
|
||||
->replace($pathRealPath, '')
|
||||
->ltrim('/\\')
|
||||
->replace('.' . $extension, '')
|
||||
->replace(['/', '\\'], '.')
|
||||
->pipe(fn($str) => $this->handleIndexComponents($str));
|
||||
|
||||
$components[] = [
|
||||
"path" => LaravelVsCode::relativePath($realPath),
|
||||
"isVendor" => LaravelVsCode::isVendor($realPath),
|
||||
"key" => $keyCallback ? $keyCallback($key) : $key,
|
||||
];
|
||||
}
|
||||
|
||||
return $components;
|
||||
}
|
||||
|
||||
protected function getStandardClasses()
|
||||
{
|
||||
$path = app_path('View/Components');
|
||||
|
||||
$appNamespace = collect($this->autoloaded)
|
||||
->filter(fn($paths) => in_array(app_path(), $paths))
|
||||
->keys()
|
||||
->first() ?? '';
|
||||
|
||||
return collect($this->findFiles(
|
||||
$path,
|
||||
'php',
|
||||
fn($key) => $key->explode('.')
|
||||
->map(fn($p) => \Illuminate\Support\Str::kebab($p))
|
||||
->implode('.'),
|
||||
))->map(function ($item) use ($appNamespace) {
|
||||
$class = str($item['path'])
|
||||
->after('View/Components/')
|
||||
->replace('.php', '')
|
||||
->replace('/', '\\')
|
||||
->prepend($appNamespace . 'View\\Components\\')
|
||||
->toString();
|
||||
|
||||
if (!class_exists($class)) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
$reflection = new \ReflectionClass($class);
|
||||
$parameters = collect($reflection->getConstructor()?->getParameters() ?? [])
|
||||
->filter(fn($p) => $p->isPromoted())
|
||||
->flatMap(fn($p) => [$p->getName() => $p->isOptional() ? $p->getDefaultValue() : null])
|
||||
->all();
|
||||
|
||||
$props = collect($reflection->getProperties())
|
||||
->filter(fn($p) => $p->isPublic() && $p->getDeclaringClass()->getName() === $class)
|
||||
->map(fn($p) => [
|
||||
'name' => \Illuminate\Support\Str::kebab($p->getName()),
|
||||
'type' => (string) ($p->getType() ?? 'mixed'),
|
||||
'default' => $p->getDefaultValue() ?? $parameters[$p->getName()] ?? null,
|
||||
]);
|
||||
|
||||
[$except, $props] = $props->partition(fn($p) => $p['name'] === 'except');
|
||||
|
||||
if ($except->isNotEmpty()) {
|
||||
$except = $except->first()['default'];
|
||||
$props = $props->reject(fn($p) => in_array($p['name'], $except));
|
||||
}
|
||||
|
||||
return [
|
||||
...$item,
|
||||
'props' => $props,
|
||||
];
|
||||
})->all();
|
||||
}
|
||||
|
||||
protected function getAliases()
|
||||
{
|
||||
$components = [];
|
||||
|
||||
foreach (\Illuminate\Support\Facades\Blade::getClassComponentAliases() as $key => $class) {
|
||||
if (class_exists($class)) {
|
||||
$reflection = new ReflectionClass($class);
|
||||
|
||||
$components[] = [
|
||||
"path" => LaravelVsCode::relativePath($reflection->getFileName()),
|
||||
"isVendor" => LaravelVsCode::isVendor($reflection->getFileName()),
|
||||
"key" => $key,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $components;
|
||||
}
|
||||
|
||||
protected function getAnonymousNamespaced()
|
||||
{
|
||||
$components = [];
|
||||
|
||||
foreach (\Illuminate\Support\Facades\Blade::getAnonymousComponentNamespaces() as $key => $dir) {
|
||||
$path = collect([$dir, resource_path('views/' . $dir)])->first(fn($p) => is_dir($p));
|
||||
|
||||
if (!$path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array_push(
|
||||
$components,
|
||||
...$this->findFiles(
|
||||
$path,
|
||||
'blade.php',
|
||||
fn($k) => $k->kebab()->prepend($key . "::"),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $components;
|
||||
}
|
||||
|
||||
protected function getAnonymous()
|
||||
{
|
||||
$components = [];
|
||||
|
||||
foreach (\Illuminate\Support\Facades\Blade::getAnonymousComponentPaths() as $item) {
|
||||
array_push(
|
||||
$components,
|
||||
...$this->findFiles(
|
||||
$item['path'],
|
||||
'blade.php',
|
||||
function (\Illuminate\Support\Stringable $key) use ($item) {
|
||||
$prefix = $item['prefix'] ? $item['prefix'] . '::' : '';
|
||||
$key = $key->kebab();
|
||||
$keys = [];
|
||||
|
||||
$keys[] = $key->prepend($prefix);
|
||||
|
||||
if ($item['prefix'] === 'flux') {
|
||||
$keys[] = $key->prepend('flux:');
|
||||
}
|
||||
|
||||
return $keys;
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
if (!in_array($item['prefix'], $this->prefixes)) {
|
||||
$this->prefixes[] = $item['prefix'];
|
||||
}
|
||||
}
|
||||
|
||||
return $components;
|
||||
}
|
||||
|
||||
protected function getVendorComponents(): array
|
||||
{
|
||||
$components = [];
|
||||
|
||||
/** @var \Illuminate\View\Factory $view */
|
||||
$view = \Illuminate\Support\Facades\App::make('view');
|
||||
|
||||
/** @var \Illuminate\View\FileViewFinder $finder */
|
||||
$finder = $view->getFinder();
|
||||
|
||||
/** @var array<string, array<int, string>> $views */
|
||||
$views = $finder->getHints();
|
||||
|
||||
foreach ($views as $key => $paths) {
|
||||
foreach ($paths as $path) {
|
||||
$path .= '/components';
|
||||
|
||||
if (!is_dir($path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array_push(
|
||||
$components,
|
||||
...$this->findFiles(
|
||||
$path,
|
||||
'blade.php',
|
||||
fn (\Illuminate\Support\Stringable $k) => $k->kebab()->prepend($key.'::'),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $components;
|
||||
}
|
||||
|
||||
protected function handleIndexComponents($str)
|
||||
{
|
||||
if ($str->endsWith('.index')) {
|
||||
return $str->replaceLast('.index', '');
|
||||
}
|
||||
|
||||
if (!$str->contains('.')) {
|
||||
return $str;
|
||||
}
|
||||
|
||||
$parts = $str->explode('.');
|
||||
|
||||
if ($parts->slice(-2)->unique()->count() === 1) {
|
||||
$parts->pop();
|
||||
|
||||
return str($parts->implode('.'));
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
protected function getNamespaced()
|
||||
{
|
||||
$namespaced = \Illuminate\Support\Facades\Blade::getClassComponentNamespaces();
|
||||
$components = [];
|
||||
|
||||
foreach ($namespaced as $key => $classNamespace) {
|
||||
$path = $this->getNamespacePath($classNamespace);
|
||||
|
||||
if (!$path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array_push(
|
||||
$components,
|
||||
...$this->findFiles(
|
||||
$path,
|
||||
'php',
|
||||
fn($k) => $k->kebab()->prepend($key . "::"),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $components;
|
||||
}
|
||||
|
||||
protected function getNamespacePath($classNamespace)
|
||||
{
|
||||
foreach ($this->autoloaded as $ns => $paths) {
|
||||
if (!str_starts_with($classNamespace, $ns)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($paths as $p) {
|
||||
$dir = str($classNamespace)
|
||||
->replace($ns, '')
|
||||
->replace('\\', '/')
|
||||
->prepend($p . DIRECTORY_SEPARATOR)
|
||||
->toString();
|
||||
|
||||
if (is_dir($dir)) {
|
||||
return $dir;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function setProps($groups)
|
||||
{
|
||||
try {
|
||||
$compiler = app('blade.compiler');
|
||||
} catch (\Throwable $e) {
|
||||
return $groups;
|
||||
}
|
||||
|
||||
return $groups->map(function ($group) use ($compiler) {
|
||||
return $group->transform(function ($component) use ($compiler) {
|
||||
if (isset($component['props'])) {
|
||||
return $component;
|
||||
}
|
||||
|
||||
if (!str($component['path'])->endsWith('.blade.php')) {
|
||||
return $component;
|
||||
}
|
||||
|
||||
if (! $props = $this->parseProps($compiler, $component)) {
|
||||
return $component;
|
||||
}
|
||||
|
||||
return array_merge($component, ['props' => $props]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected function parseProps($compiler, array $component): ?string
|
||||
{
|
||||
$content = file_get_contents(base_path($component['path']));
|
||||
|
||||
$result = '';
|
||||
|
||||
$compiler->directive('props', function ($expression) use (&$result) {
|
||||
return $result = $expression;
|
||||
});
|
||||
|
||||
$compiler->compileString($content);
|
||||
|
||||
if (empty($result)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return '@props('.$result.')';
|
||||
}
|
||||
};
|
||||
|
||||
echo json_encode($components->all());
|
||||
|
||||
echo LaravelVsCode::outputMarker('END_OUTPUT');
|
||||
|
||||
exit(0);
|
||||
350
vendor/_laravel_ide/discover-ec2e9975ebf5755f76a162b3dce99361.php
vendored
Normal file
350
vendor/_laravel_ide/discover-ec2e9975ebf5755f76a162b3dce99361.php
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
|
||||
|
||||
error_reporting(E_ERROR | E_PARSE);
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
require_once __DIR__ . '/../autoload.php';
|
||||
|
||||
class LaravelVsCode
|
||||
{
|
||||
public static function relativePath($path)
|
||||
{
|
||||
if (!str_contains($path, base_path())) {
|
||||
return (string) $path;
|
||||
}
|
||||
|
||||
return ltrim(str_replace(base_path(), '', realpath($path) ?: $path), DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
public static function isVendor($path)
|
||||
{
|
||||
return str_contains($path, base_path("vendor"));
|
||||
}
|
||||
|
||||
public static function outputMarker($key)
|
||||
{
|
||||
return '__VSCODE_LARAVEL_' . $key . '__';
|
||||
}
|
||||
|
||||
public static function startupError(\Throwable $e)
|
||||
{
|
||||
throw new Error(self::outputMarker('STARTUP_ERROR') . ': ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$app = require_once __DIR__ . '/../../bootstrap/app.php';
|
||||
} catch (\Throwable $e) {
|
||||
LaravelVsCode::startupError($e);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$app->register(new class($app) extends \Illuminate\Support\ServiceProvider
|
||||
{
|
||||
public function boot()
|
||||
{
|
||||
config([
|
||||
'logging.channels.null' => [
|
||||
'driver' => 'monolog',
|
||||
'handler' => \Monolog\Handler\NullHandler::class,
|
||||
],
|
||||
'logging.default' => 'null',
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||
$kernel->bootstrap();
|
||||
} catch (\Throwable $e) {
|
||||
LaravelVsCode::startupError($e);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo LaravelVsCode::outputMarker('START_OUTPUT');
|
||||
|
||||
$livewire = new class {
|
||||
protected $namespaces;
|
||||
protected $paths;
|
||||
protected $extensions = [".blade.php", ".php", ".js", ".global.css", ".css", ".test.php"];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->namespaces = collect(
|
||||
config("livewire.component_namespaces", [])
|
||||
)->map(LaravelVsCode::relativePath(...));
|
||||
|
||||
$this->paths = collect($this->namespaces->values())
|
||||
->merge(config("livewire.component_locations", []))
|
||||
->unique()
|
||||
->map(LaravelVsCode::relativePath(...));
|
||||
}
|
||||
|
||||
public function parse(\Illuminate\Support\Collection $views)
|
||||
{
|
||||
return $this->isVersionFour()
|
||||
? $this->parseLivewireFour($views)
|
||||
: $this->parseLivewireThree($views);
|
||||
}
|
||||
|
||||
protected function parseLivewireFour(\Illuminate\Support\Collection $views)
|
||||
{
|
||||
return $views
|
||||
->map(function (array $view) {
|
||||
if (!$this->pathExists($view["path"])) {
|
||||
return $view;
|
||||
}
|
||||
|
||||
if (is_null($component = $this->getComponent($key = $this->key($view)))) {
|
||||
return $view;
|
||||
}
|
||||
|
||||
$files = $this->files($view);
|
||||
|
||||
if (count($files) === 1 && !str($view['path'])->endsWith('.blade.php')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_merge($view, [
|
||||
"key" => $key,
|
||||
'livewire' => [
|
||||
'props' => $this->getProps($component),
|
||||
'files' => $files,
|
||||
],
|
||||
]);
|
||||
})
|
||||
->whereNotNull()
|
||||
->unique('key')
|
||||
->values();
|
||||
}
|
||||
|
||||
protected function parseLivewireThree(\Illuminate\Support\Collection $views)
|
||||
{
|
||||
return $views->map(function (array $view) {
|
||||
if (!str($view['key'])->startsWith('livewire.')) {
|
||||
return $view;
|
||||
}
|
||||
|
||||
$key = str($view['key'])->after('livewire.')->value();
|
||||
|
||||
if (is_null($component = $this->getComponent($key))) {
|
||||
return $view;
|
||||
}
|
||||
|
||||
return array_merge($view, [
|
||||
'livewire' => [
|
||||
'props' => $this->getProps($component),
|
||||
'files' => [$view['path']],
|
||||
],
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
protected function isVersionFour(): bool
|
||||
{
|
||||
return property_exists(\Livewire\LivewireManager::class, "v4") &&
|
||||
\Livewire\LivewireManager::$v4;
|
||||
}
|
||||
|
||||
protected function pathExists(string $path): bool
|
||||
{
|
||||
return $this->paths->contains(fn (string $item) => str($path)->contains($item));
|
||||
}
|
||||
|
||||
protected function getComponent(string $key)
|
||||
{
|
||||
try {
|
||||
return app("livewire")->new($key);
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getProps($component): array
|
||||
{
|
||||
return array_map(function ($prop) use ($component) {
|
||||
$reflection = new \ReflectionProperty($component, $prop);
|
||||
|
||||
return [
|
||||
'name' => $prop,
|
||||
'type' => (string) $reflection->getType() ?: 'mixed',
|
||||
'hasDefaultValue' => $reflection->hasDefaultValue(),
|
||||
'defaultValue' => $this->formatDefaultValue($reflection->getDefaultValue()),
|
||||
];
|
||||
}, array_keys($component->all()));
|
||||
}
|
||||
|
||||
protected function formatDefaultValue(mixed $value)
|
||||
{
|
||||
return is_string($value) ? "'{$value}'": $value;
|
||||
}
|
||||
|
||||
protected function key(array $view): string
|
||||
{
|
||||
return str($view["key"])
|
||||
->replace("⚡", "")
|
||||
->when($this->isMfc($view), fn ($key) => $key->beforeLast("."))
|
||||
->value();
|
||||
}
|
||||
|
||||
protected function files(array $view): array
|
||||
{
|
||||
if (! $this->isMfc($view)) {
|
||||
return [$view['path']];
|
||||
}
|
||||
|
||||
$filePathWithoutExtension = str($view["path"])->replace($this->extensions, "");
|
||||
|
||||
return collect($this->extensions)
|
||||
->map(fn (string $extension) => $filePathWithoutExtension->append($extension))
|
||||
->filter(fn (string $path) => \Illuminate\Support\Facades\File::exists($path))
|
||||
->all();
|
||||
}
|
||||
|
||||
protected function isMfc(array $view): bool
|
||||
{
|
||||
$directory = str($view["path"])
|
||||
->replace("⚡", "")
|
||||
->dirname()
|
||||
->afterLast(DIRECTORY_SEPARATOR);
|
||||
|
||||
$file = str($view["path"])
|
||||
->replace("⚡", "")
|
||||
->basename()
|
||||
->replace($this->extensions, "");
|
||||
|
||||
$class = str($view["path"])
|
||||
->dirname()
|
||||
->append(DIRECTORY_SEPARATOR . $file . ".php");
|
||||
|
||||
return $directory->is($file)
|
||||
&& \Illuminate\Support\Facades\File::exists($class);
|
||||
}
|
||||
};
|
||||
|
||||
$blade = new class ($livewire) {
|
||||
public function __construct(protected $livewire)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function getAllViews()
|
||||
{
|
||||
$finder = app("view")->getFinder();
|
||||
|
||||
$paths = collect($finder->getPaths())->flatMap(fn($path) => $this->findViews($path));
|
||||
|
||||
$hints = collect($finder->getHints())
|
||||
->filter(
|
||||
fn ($_, $key) => ! (strlen($key) === 32 && ctype_xdigit($key))
|
||||
)->flatMap(
|
||||
fn($paths, $key) => collect($paths)->flatMap(
|
||||
fn($path) => collect($this->findViews($path))->map(
|
||||
fn($value) => array_merge($value, ["key" => "{$key}::{$value["key"]}"])
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
[$local, $vendor] = $paths
|
||||
->merge($hints)
|
||||
->values()
|
||||
->partition(fn($v) => !$v["isVendor"]);
|
||||
|
||||
return $local
|
||||
->sortBy("key", SORT_NATURAL)
|
||||
->merge($vendor->sortBy("key", SORT_NATURAL))
|
||||
->pipe($this->livewire->parse(...));
|
||||
}
|
||||
|
||||
public function getAllComponents()
|
||||
{
|
||||
$namespaced = \Illuminate\Support\Facades\Blade::getClassComponentNamespaces();
|
||||
$autoloaded = require base_path("vendor/composer/autoload_psr4.php");
|
||||
$components = [];
|
||||
|
||||
foreach ($namespaced as $key => $ns) {
|
||||
$path = null;
|
||||
|
||||
foreach ($autoloaded as $namespace => $paths) {
|
||||
if (str_starts_with($ns, $namespace)) {
|
||||
foreach ($paths as $p) {
|
||||
$test = str($ns)->replace($namespace, '')->replace('\\', '/')->prepend($p . DIRECTORY_SEPARATOR)->toString();
|
||||
|
||||
if (is_dir($test)) {
|
||||
$path = $test;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$files = \Symfony\Component\Finder\Finder::create()
|
||||
->files()
|
||||
->name("*.php")
|
||||
->in($path);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$realPath = $file->getRealPath();
|
||||
|
||||
$components[] = [
|
||||
"path" => LaravelVsCode::relativePath($realPath),
|
||||
"isVendor" => str_contains($realPath, base_path("vendor")),
|
||||
"key" => str($realPath)
|
||||
->replace(realpath($path), "")
|
||||
->replace(".php", "")
|
||||
->ltrim(DIRECTORY_SEPARATOR)
|
||||
->replace(DIRECTORY_SEPARATOR, ".")
|
||||
->kebab()
|
||||
->prepend($key . "::"),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $components;
|
||||
}
|
||||
|
||||
protected function findViews($path)
|
||||
{
|
||||
$paths = [];
|
||||
|
||||
if (!is_dir($path)) {
|
||||
return $paths;
|
||||
}
|
||||
|
||||
$finder = app("view")->getFinder();
|
||||
$extensions = array_map(fn($extension) => ".{$extension}", $finder->getExtensions());
|
||||
|
||||
$files = \Symfony\Component\Finder\Finder::create()
|
||||
->files()
|
||||
->name(array_map(fn ($ext) => "*{$ext}", $extensions))
|
||||
->in($path);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$paths[] = [
|
||||
"path" => LaravelVsCode::relativePath($file->getRealPath()),
|
||||
"isVendor" => str_contains($file->getRealPath(), base_path("vendor")),
|
||||
"key" => str($file->getRealPath())
|
||||
->replace(realpath($path), "")
|
||||
->replace($extensions, "")
|
||||
->ltrim(DIRECTORY_SEPARATOR)
|
||||
->replace(DIRECTORY_SEPARATOR, "."),
|
||||
];
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
};
|
||||
|
||||
echo json_encode($blade->getAllViews()->merge($blade->getAllComponents()));
|
||||
|
||||
echo LaravelVsCode::outputMarker('END_OUTPUT');
|
||||
|
||||
exit(0);
|
||||
Reference in New Issue
Block a user