import and export
This commit is contained in:
+1
-1
@@ -69,7 +69,7 @@ export function apiPost(url, body, options = {}) {
|
|||||||
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').content,
|
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').content,
|
||||||
...options.headers,
|
...options.headers,
|
||||||
},
|
},
|
||||||
});
|
}).then((r) => r.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function apiGet(url, options = {}) {
|
export function apiGet(url, options = {}) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import {getContext} from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
import { apiPost } from "../../helpers";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
export let selected;
|
export let selected;
|
||||||
@@ -8,10 +9,9 @@
|
|||||||
|
|
||||||
function deleteRecords(e) {
|
function deleteRecords(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
axios
|
apiPost(channel.lucentUrl + "/records/delete", {
|
||||||
.post(channel.lucentUrl + "/records/delete", {
|
ids: selected.map((s) => s.id),
|
||||||
ids: selected.map((s) => s.id),
|
})
|
||||||
})
|
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
})
|
})
|
||||||
@@ -21,11 +21,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function changeStatus(e, status) {
|
function changeStatus(e, status) {
|
||||||
axios
|
apiPost(channel.lucentUrl + "/records/status/" + status, {
|
||||||
.post(channel.lucentUrl + "/records/status/" + status, {
|
schemaName: schema.name,
|
||||||
schemaName: schema.name,
|
records: selected,
|
||||||
records: selected
|
})
|
||||||
})
|
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
})
|
})
|
||||||
@@ -38,44 +37,44 @@
|
|||||||
<div style="display: flex;align-items: center; gap: 8px">
|
<div style="display: flex;align-items: center; gap: 8px">
|
||||||
<span class="me-2">{selected.length} records selected</span>
|
<span class="me-2">{selected.length} records selected</span>
|
||||||
<button
|
<button
|
||||||
on:click|preventDefault={(e) => changeStatus(e, "published")}
|
on:click|preventDefault={(e) => changeStatus(e, "published")}
|
||||||
type="button"
|
type="button"
|
||||||
class="button">Publish
|
class="button"
|
||||||
</button
|
>Publish
|
||||||
>
|
</button>
|
||||||
<button
|
<button
|
||||||
on:click|preventDefault={(e) => changeStatus(e, "draft")}
|
on:click|preventDefault={(e) => changeStatus(e, "draft")}
|
||||||
type="button"
|
type="button"
|
||||||
class="button">Make Draft
|
class="button"
|
||||||
</button
|
>Make Draft
|
||||||
>
|
</button>
|
||||||
{#if filter["status_in"] === "trashed"}
|
{#if filter["status_in"] === "trashed"}
|
||||||
<button
|
<button
|
||||||
on:click|preventDefault={(e) => changeStatus(e, "published")}
|
on:click|preventDefault={(e) => changeStatus(e, "published")}
|
||||||
type="button"
|
type="button"
|
||||||
class="button">Publish
|
class="button"
|
||||||
</button
|
>Publish
|
||||||
>
|
</button>
|
||||||
{#if schema.hasDrafts}
|
{#if schema.hasDrafts}
|
||||||
<button
|
<button
|
||||||
on:click|preventDefault={(e) => changeStatus(e, "draft")}
|
on:click|preventDefault={(e) => changeStatus(e, "draft")}
|
||||||
type="button"
|
type="button"
|
||||||
class="button">Make Draft
|
class="button"
|
||||||
</button
|
>Make Draft
|
||||||
>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
<button
|
||||||
on:click|preventDefault={deleteRecords}
|
on:click|preventDefault={deleteRecords}
|
||||||
type="button"
|
type="button"
|
||||||
class="button">Delete forever
|
class="button"
|
||||||
</button
|
>Delete forever
|
||||||
>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click|preventDefault={(e) => changeStatus(e, "trashed")}
|
on:click|preventDefault={(e) => changeStatus(e, "trashed")}
|
||||||
class="button">Move to trash
|
class="button"
|
||||||
</button
|
>Move to trash
|
||||||
>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import {getContext} from "svelte";
|
import { getContext } from "svelte";
|
||||||
import Icon from "../../common/Icon.svelte";
|
import Icon from "../../common/Icon.svelte";
|
||||||
import Dropdown from "../../common/Dropdown.svelte";
|
import Dropdown from "../../common/Dropdown.svelte";
|
||||||
import StatusSelect from "./StatusSelect.svelte";
|
import StatusSelect from "./StatusSelect.svelte";
|
||||||
|
import { apiPost } from "../../../helpers";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
export let schema;
|
export let schema;
|
||||||
@@ -12,45 +13,42 @@
|
|||||||
|
|
||||||
function clone(e) {
|
function clone(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
axios.post(channel.lucentUrl + "/records/clone/" + record.id).then(response => {
|
apiPost(channel.lucentUrl + "/records/clone/" + record.id)
|
||||||
window.location = channel.lucentUrl + "/records/" + response.data.id;
|
.then((response) => {
|
||||||
}).catch(error => {
|
window.location = channel.lucentUrl + "/records/" + response.id;
|
||||||
|
})
|
||||||
});
|
.catch((error) => {});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div style="display: flex;align-items: center; gap:10px;">
|
<div style="display: flex;align-items: center; gap:10px;">
|
||||||
{#if !isCreateMode}
|
{#if !isCreateMode}
|
||||||
<Dropdown >
|
<Dropdown>
|
||||||
<div slot="button">
|
<div slot="button">
|
||||||
<Icon icon="ellipsis"/>
|
<Icon icon="ellipsis" />
|
||||||
</div>
|
</div>
|
||||||
<h6 class="dropdown-header">Record Actions</h6>
|
<h6 class="dropdown-header">Record Actions</h6>
|
||||||
<a
|
<a
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
href="{channel.lucentUrl}/records/new?schema={schema.name}"
|
href="{channel.lucentUrl}/records/new?schema={schema.name}"
|
||||||
>Create new</a
|
>Create new</a
|
||||||
>
|
>
|
||||||
{#if !isCreateMode}
|
{#if !isCreateMode}
|
||||||
<a
|
<a
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
on:click={clone}
|
on:click={clone}
|
||||||
href={channel.lucentUrl}
|
href={channel.lucentUrl}
|
||||||
>
|
>
|
||||||
Clone
|
Clone
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
<a
|
<a
|
||||||
on:click|preventDefault={(e) =>
|
on:click|preventDefault={(e) => (activeContentTab = "_info")}
|
||||||
(activeContentTab = "_info")}
|
class="dropdown-item"
|
||||||
class="dropdown-item"
|
href={channel.lucentUrl}>Revisions</a
|
||||||
href="{channel.lucentUrl}">Revisions</a
|
|
||||||
>
|
>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<StatusSelect bind:status={record.status} {record}></StatusSelect>
|
<StatusSelect bind:status={record.status} {record}></StatusSelect>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use ZipArchive;
|
||||||
|
|
||||||
|
class Export extends Command
|
||||||
|
{
|
||||||
|
protected $signature = "lucent:export";
|
||||||
|
protected $prefix = "lucent_";
|
||||||
|
|
||||||
|
protected $description = "Export data and files";
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$db = config("database.connections.pgsql");
|
||||||
|
$tables = [
|
||||||
|
"lucent_records",
|
||||||
|
"lucent_revisions",
|
||||||
|
"lucent_files",
|
||||||
|
"lucent_edges",
|
||||||
|
];
|
||||||
|
|
||||||
|
$exportDir = storage_path("exports");
|
||||||
|
if (!is_dir($exportDir)) {
|
||||||
|
mkdir($exportDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stamp = now()->format("Y_m_d_His");
|
||||||
|
$sqlFile = $exportDir . "/dump_{$stamp}.sql";
|
||||||
|
$zipFile = $exportDir . "/export_{$stamp}.zip";
|
||||||
|
|
||||||
|
// Dump database
|
||||||
|
$tableArgs = collect($tables)->map(fn($t) => "-t {$t}")->join(" ");
|
||||||
|
$command = sprintf(
|
||||||
|
"PGPASSWORD=%s pg_dump -h %s -p %s -U %s -d %s %s --no-owner --no-acl > %s",
|
||||||
|
$db["password"],
|
||||||
|
$db["host"],
|
||||||
|
$db["port"],
|
||||||
|
$db["username"],
|
||||||
|
$db["database"],
|
||||||
|
$tableArgs,
|
||||||
|
$sqlFile,
|
||||||
|
);
|
||||||
|
|
||||||
|
exec($command, result_code: $code);
|
||||||
|
|
||||||
|
if ($code !== 0) {
|
||||||
|
$this->error("pg_dump failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info("Database dumped.");
|
||||||
|
|
||||||
|
// Zip SQL + files
|
||||||
|
$filesDir = Storage::disk(config("lucent.disk"))->path("files");
|
||||||
|
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
if (
|
||||||
|
$zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !==
|
||||||
|
true
|
||||||
|
) {
|
||||||
|
$this->error("Could not create zip archive.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip->addFile($sqlFile, "dump_{$stamp}.sql");
|
||||||
|
|
||||||
|
if (is_dir($filesDir)) {
|
||||||
|
$iterator = new \RecursiveIteratorIterator(
|
||||||
|
new \RecursiveDirectoryIterator(
|
||||||
|
$filesDir,
|
||||||
|
\FilesystemIterator::SKIP_DOTS,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
$relativePath =
|
||||||
|
"files/" .
|
||||||
|
ltrim(
|
||||||
|
str_replace($filesDir, "", $file->getRealPath()),
|
||||||
|
DIRECTORY_SEPARATOR,
|
||||||
|
);
|
||||||
|
$zip->addFile($file->getRealPath(), $relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip->close();
|
||||||
|
|
||||||
|
// Clean up originals
|
||||||
|
unlink($sqlFile);
|
||||||
|
|
||||||
|
$this->info("Exported to {$zipFile}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use ZipArchive;
|
||||||
|
|
||||||
|
class Import extends Command
|
||||||
|
{
|
||||||
|
protected $signature = "lucent:import";
|
||||||
|
|
||||||
|
protected $description = "Import data and files from an export archive";
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$exportDir = storage_path("exports");
|
||||||
|
|
||||||
|
if (!is_dir($exportDir)) {
|
||||||
|
$this->error("No exports directory found at {$exportDir}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$zips = glob($exportDir . "/export_*.zip");
|
||||||
|
|
||||||
|
if (empty($zips)) {
|
||||||
|
$this->error("No export archives found in {$exportDir}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rsort($zips);
|
||||||
|
$choices = array_map(fn($p) => basename($p), $zips);
|
||||||
|
|
||||||
|
$chosen = $this->choice("Select an export to import", $choices, 0);
|
||||||
|
$zipFile = $exportDir . "/" . $chosen;
|
||||||
|
|
||||||
|
$this->warn("This will REPLACE all records, revisions, edges, files data and uploaded files.");
|
||||||
|
|
||||||
|
if (!$this->confirm("Import {$chosen}?", false)) {
|
||||||
|
$this->info("Aborted.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract to temp directory
|
||||||
|
$tempDir = storage_path("exports/.import_tmp_" . uniqid());
|
||||||
|
mkdir($tempDir, 0755, true);
|
||||||
|
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
if ($zip->open($zipFile) !== true) {
|
||||||
|
$this->error("Could not open zip archive.");
|
||||||
|
$this->cleanup($tempDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip->extractTo($tempDir);
|
||||||
|
$zip->close();
|
||||||
|
|
||||||
|
// Restore database
|
||||||
|
$sqlFiles = glob($tempDir . "/*.sql");
|
||||||
|
if (empty($sqlFiles)) {
|
||||||
|
$this->error("No SQL dump found inside the archive.");
|
||||||
|
$this->cleanup($tempDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = config("database.connections.pgsql");
|
||||||
|
$tables = [
|
||||||
|
"lucent_records",
|
||||||
|
"lucent_revisions",
|
||||||
|
"lucent_files",
|
||||||
|
"lucent_edges",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Truncate existing tables before restore
|
||||||
|
$truncate = collect($tables)
|
||||||
|
->map(fn($t) => "TRUNCATE TABLE {$t} CASCADE;")
|
||||||
|
->join(" ");
|
||||||
|
|
||||||
|
$truncateCmd = sprintf(
|
||||||
|
"PGPASSWORD=%s psql -h %s -p %s -U %s -d %s -c \"%s\"",
|
||||||
|
$db["password"],
|
||||||
|
$db["host"],
|
||||||
|
$db["port"],
|
||||||
|
$db["username"],
|
||||||
|
$db["database"],
|
||||||
|
$truncate,
|
||||||
|
);
|
||||||
|
|
||||||
|
exec($truncateCmd, result_code: $truncateCode);
|
||||||
|
|
||||||
|
if ($truncateCode !== 0) {
|
||||||
|
$this->error("Failed to truncate existing tables.");
|
||||||
|
$this->cleanup($tempDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$restoreCmd = sprintf(
|
||||||
|
"PGPASSWORD=%s psql -h %s -p %s -U %s -d %s -f %s",
|
||||||
|
$db["password"],
|
||||||
|
$db["host"],
|
||||||
|
$db["port"],
|
||||||
|
$db["username"],
|
||||||
|
$db["database"],
|
||||||
|
$sqlFiles[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
exec($restoreCmd, result_code: $restoreCode);
|
||||||
|
|
||||||
|
if ($restoreCode !== 0) {
|
||||||
|
$this->error("Database restore failed.");
|
||||||
|
$this->cleanup($tempDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info("Database restored.");
|
||||||
|
|
||||||
|
// Replace files
|
||||||
|
$srcFilesDir = $tempDir . "/files";
|
||||||
|
|
||||||
|
if (is_dir($srcFilesDir)) {
|
||||||
|
$disk = Storage::disk(config("lucent.disk"));
|
||||||
|
$destFilesDir = $disk->path("files");
|
||||||
|
|
||||||
|
// Remove existing files directory
|
||||||
|
if (is_dir($destFilesDir)) {
|
||||||
|
exec("rm -rf " . escapeshellarg($destFilesDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(
|
||||||
|
sprintf("cp -R %s %s", escapeshellarg($srcFilesDir), escapeshellarg($destFilesDir)),
|
||||||
|
result_code: $copyCode,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($copyCode !== 0) {
|
||||||
|
$this->error("Failed to restore files.");
|
||||||
|
$this->cleanup($tempDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info("Files restored.");
|
||||||
|
} else {
|
||||||
|
$this->warn("No files directory found in archive — skipping file restore.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cleanup($tempDir);
|
||||||
|
$this->info("Import complete.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanup(string $dir): void
|
||||||
|
{
|
||||||
|
if (is_dir($dir)) {
|
||||||
|
exec("rm -rf " . escapeshellarg($dir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -84,8 +84,8 @@ class RecordController extends Controller
|
|||||||
->childrenFields($schema?->visible ?? [])
|
->childrenFields($schema?->visible ?? [])
|
||||||
->childrenDepth(1)
|
->childrenDepth(1)
|
||||||
->parentsDepth(0)
|
->parentsDepth(0)
|
||||||
|
|
||||||
->runWithCount();
|
->runWithCount();
|
||||||
|
|
||||||
$records = $graph->getRootRecords()->toArray();
|
$records = $graph->getRootRecords()->toArray();
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ use Lucent\Commands\LiveLink;
|
|||||||
use Lucent\Commands\RebuildThumbnails;
|
use Lucent\Commands\RebuildThumbnails;
|
||||||
use Lucent\Commands\RemoveOrphanEdges;
|
use Lucent\Commands\RemoveOrphanEdges;
|
||||||
use Lucent\Commands\SetupDatabase;
|
use Lucent\Commands\SetupDatabase;
|
||||||
|
use Lucent\Commands\Export;
|
||||||
|
use Lucent\Commands\Import;
|
||||||
use Lucent\Data\ChannelAuth;
|
use Lucent\Data\ChannelAuth;
|
||||||
use Lucent\File\FileService;
|
use Lucent\File\FileService;
|
||||||
use Lucent\Query\DatabaseGraph\DatabaseGraph;
|
use Lucent\Query\DatabaseGraph\DatabaseGraph;
|
||||||
@@ -96,6 +98,8 @@ class LucentServiceProvider extends ServiceProvider
|
|||||||
RemoveOrphanEdges::class,
|
RemoveOrphanEdges::class,
|
||||||
SetupDatabase::class,
|
SetupDatabase::class,
|
||||||
GenerateCollectionSchema::class,
|
GenerateCollectionSchema::class,
|
||||||
|
Export::class,
|
||||||
|
Import::class,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ final class Query
|
|||||||
$resultChildrenEdges,
|
$resultChildrenEdges,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$resultParentSourceTargetIds = [];
|
$resultParentSourceTargetIds = [];
|
||||||
$resultParentEdges = [];
|
$resultParentEdges = [];
|
||||||
if ($this->options->parentsDepth > 0 && !empty($ids)) {
|
if ($this->options->parentsDepth > 0 && !empty($ids)) {
|
||||||
@@ -126,6 +127,7 @@ final class Query
|
|||||||
$queryRecord->isRoot = data_get($recordData, "isRoot") === true;
|
$queryRecord->isRoot = data_get($recordData, "isRoot") === true;
|
||||||
return $queryRecord;
|
return $queryRecord;
|
||||||
})
|
})
|
||||||
|
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
||||||
$queryEdges = collect($edges)
|
$queryEdges = collect($edges)
|
||||||
@@ -222,7 +224,9 @@ final class Query
|
|||||||
$query = Database::make()->table("lucent_records");
|
$query = Database::make()->table("lucent_records");
|
||||||
$query = $this->parseFilters($query);
|
$query = $this->parseFilters($query);
|
||||||
$query = $this->findNotLinked($query);
|
$query = $this->findNotLinked($query);
|
||||||
|
|
||||||
$graph = $this->run();
|
$graph = $this->run();
|
||||||
|
|
||||||
$graph->total = $query->count();
|
$graph->total = $query->count();
|
||||||
return $graph;
|
return $graph;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,22 +7,20 @@ use Lucent\Schema\FieldInterface;
|
|||||||
|
|
||||||
class InputFormatter
|
class InputFormatter
|
||||||
{
|
{
|
||||||
|
public function __construct(public ChannelService $channelService) {}
|
||||||
public function __construct(
|
|
||||||
public ChannelService $channelService,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function fill(string $schemaName, RecordData $input): RecordData
|
public function fill(string $schemaName, RecordData $input): RecordData
|
||||||
{
|
{
|
||||||
|
|
||||||
$schema = $this->channelService->getSchema($schemaName)->get();
|
$schema = $this->channelService->getSchema($schemaName)->get();
|
||||||
|
|
||||||
$data = $schema->fields->reduce(fn(array $carry, FieldInterface $field) => $field->format($input->toArray(), $carry), []);
|
$data = $schema->fields->reduce(
|
||||||
|
fn(array $carry, FieldInterface $field) => $field->format(
|
||||||
|
$input->toArray(),
|
||||||
|
$carry,
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
return new RecordData($data);
|
return new RecordData($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-12
@@ -15,34 +15,37 @@ class Text implements FieldInterface, RequiredInterface
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
public string $name,
|
public string $name,
|
||||||
public string $label,
|
public string $label,
|
||||||
public bool $required = false,
|
public bool $required = false,
|
||||||
public bool $nullable = false,
|
public bool $nullable = false,
|
||||||
public ?int $min = null,
|
public ?int $min = null,
|
||||||
public ?int $max = null,
|
public ?int $max = null,
|
||||||
public string $help = "",
|
public string $help = "",
|
||||||
public string $default = "",
|
public string $default = "",
|
||||||
public bool $readonly = false,
|
public bool $readonly = false,
|
||||||
public string $optionsFrom = "",
|
public string $optionsFrom = "",
|
||||||
public string $optionsField = "",
|
public string $optionsField = "",
|
||||||
public bool $optionsSuggest = false,
|
public bool $optionsSuggest = false,
|
||||||
public ?array $selectOptions = null,
|
public ?array $selectOptions = null,
|
||||||
public string $group = "",
|
public string $group = "",
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
$this->info = new FieldInfo("text", "Text", FieldType::STRING);
|
$this->info = new FieldInfo("text", "Text", FieldType::STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function format(array $input, array $output): array
|
public function format(array $input, array $output): array
|
||||||
{
|
{
|
||||||
$value = !empty($input[$this->name]) ? (string)$input[$this->name] : null;
|
$value = !empty($input[$this->name])
|
||||||
$output[$this->name] = (new Nullable($this->nullable, $value, ""))->value();
|
? (string) $input[$this->name]
|
||||||
|
: null;
|
||||||
|
$output[$this->name] = Nullable::make(
|
||||||
|
$this->nullable,
|
||||||
|
$value,
|
||||||
|
"",
|
||||||
|
)->value();
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function failRequired(mixed $value): bool
|
public function failRequired(mixed $value): bool
|
||||||
{
|
{
|
||||||
return empty(trim($value));
|
return empty(trim($value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user