mirror of
https://github.com/flextype/flextype.git
synced 2025-08-06 13:16:45 +02:00
feat(entires): draft code for import/export
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Flextype - Hybrid Content Management System with the freedom of a headless CMS
|
||||
* and with the full functionality of a traditional CMS!
|
||||
*
|
||||
* Copyright (c) Sergey Romanenko (https://awilum.github.io)
|
||||
*
|
||||
* Licensed under The MIT License.
|
||||
*
|
||||
* For full copyright and license information, please see the LICENSE
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*/
|
||||
|
||||
namespace Flextype\Console\Commands\Entries;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function array_push;
|
||||
use function count;
|
||||
use function Flextype\collection;
|
||||
use function Flextype\registry;
|
||||
use function Flextype\entries;
|
||||
use function Flextype\serializers;
|
||||
use function Glowy\Strings\strings;
|
||||
use function Glowy\Filesystem\filesystem;
|
||||
use function parse_str;
|
||||
use function Thermage\div;
|
||||
use function Thermage\renderToString;
|
||||
use function Thermage\span;
|
||||
|
||||
use const PHP_EOL;
|
||||
|
||||
class EntriesExportCommand extends Command
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setName('entries:export');
|
||||
$this->setDescription('Export entry.');
|
||||
$this->addArgument('id', InputArgument::OPTIONAL, 'Unique identifier of the entry.');
|
||||
$this->addArgument('options', InputArgument::OPTIONAL, 'Options array.');
|
||||
$this->addOption('collection', null, InputOption::VALUE_NONE, 'Set this flag to fetch entries collection.');
|
||||
$this->addOption('find-depth-from', null, InputOption::VALUE_OPTIONAL, 'Restrict the entries files depth of traversing from.');
|
||||
$this->addOption('find-depth-to', null, InputOption::VALUE_OPTIONAL, 'Restrict the entries files depth of traversing to.');
|
||||
$this->addOption('find-date-from', null, InputOption::VALUE_OPTIONAL, 'Restrict the entries files by a date range from.');
|
||||
$this->addOption('find-date-to', null, InputOption::VALUE_OPTIONAL, 'Restrict the entries filesby a date range to.');
|
||||
$this->addOption('find-size-from', null, InputOption::VALUE_OPTIONAL, 'Restrict the entries files by a size range from.');
|
||||
$this->addOption('find-size-to', null, InputOption::VALUE_OPTIONAL, 'Restrict the entries files by a size range to.');
|
||||
$this->addOption('find-exclude', null, InputOption::VALUE_OPTIONAL, 'Exclude directories from matching.');
|
||||
$this->addOption('find-contains', null, InputOption::VALUE_OPTIONAL, 'Find entries files by content.');
|
||||
$this->addOption('find-not-contains', null, InputOption::VALUE_OPTIONAL, 'Find entries files by content excludes files containing given pattern.');
|
||||
$this->addOption('find-path', null, InputOption::VALUE_OPTIONAL, 'Find entries files and directories by path.');
|
||||
$this->addOption('find-sort-by-key', null, InputOption::VALUE_OPTIONAL, 'Sort the entries files and directories by the last accessed, changed or modified time. Values: atime, mtime, ctime.');
|
||||
$this->addOption('find-sort-by-direction', null, InputOption::VALUE_OPTIONAL, 'Sort the entries files and directories by direction. Order direction: DESC (descending) or ASC (ascending)');
|
||||
$this->addOption('filter-return', null, InputOption::VALUE_OPTIONAL, 'Return items. Valid values: all, first, last, next, random, shuffle');
|
||||
$this->addOption('filter-group-by', null, InputOption::VALUE_OPTIONAL, 'Group array collection by key.');
|
||||
$this->addOption('filter-offset', null, InputOption::VALUE_OPTIONAL, 'Extract a slice of the current array collection with specific offset.');
|
||||
$this->addOption('filter-limit', null, InputOption::VALUE_OPTIONAL, 'Extract a slice of the current array collection with offset 0 and specific length.');
|
||||
$this->addOption('filter-sort-by-key', null, InputOption::VALUE_OPTIONAL, 'Sort array collection by key.');
|
||||
$this->addOption('filter-sort-by-direction', null, InputOption::VALUE_OPTIONAL, 'Sort array collection by direction. Order direction: DESC (descending) or ASC (ascending)');
|
||||
$this->addOption('filter-where', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Filters the array collection fields by a given condition.');
|
||||
$this->addOption('path', null, InputOption::VALUE_OPTIONAL, 'Export path.');
|
||||
$this->addOption('filename', null, InputOption::VALUE_OPTIONAL, 'Export filename.');
|
||||
$this->addOption('extension', null, InputOption::VALUE_OPTIONAL, 'Export extension.');
|
||||
$this->addOption('serializer', null, InputOption::VALUE_OPTIONAL, 'Export serializer.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$id = $input->getArgument('id') ? $input->getArgument('id') : '';
|
||||
$options = [];
|
||||
|
||||
if ($input->getArgument('options')) {
|
||||
if (strings($input->getArgument('options'))->isJson()) {
|
||||
$options = serializers()->json()->decode($input->getArgument('options'));
|
||||
} else {
|
||||
parse_str($input->getArgument('options'), $options);
|
||||
}
|
||||
}
|
||||
|
||||
$input->getOption('collection') and $options['collection'] = true;
|
||||
|
||||
if ($input->getOption('find-depth-from') || $input->getOption('find-depth-to')) {
|
||||
$options['find']['depth'] = [];
|
||||
$input->getOption('find-depth-from') and array_push($options['find']['depth'], $input->getOption('find-depth-from'));
|
||||
$input->getOption('find-depth-to') and array_push($options['find']['depth'], $input->getOption('find-depth-to'));
|
||||
}
|
||||
|
||||
if ($input->getOption('find-date-from') || $input->getOption('find-date-to')) {
|
||||
$options['find']['date'] = [];
|
||||
$input->getOption('find-date-from') and array_push($options['find']['date'], $input->getOption('find-date-from'));
|
||||
$input->getOption('find-date-to') and array_push($options['find']['date'], $input->getOption('find-date-to'));
|
||||
}
|
||||
|
||||
if ($input->getOption('find-size-from') || $input->getOption('find-size-to')) {
|
||||
$options['find']['size'] = [];
|
||||
$input->getOption('find-size-from') and array_push($options['find']['size'], $input->getOption('find-size-from'));
|
||||
$input->getOption('find-size-to') and array_push($options['find']['size'], $input->getOption('find-size-to'));
|
||||
}
|
||||
|
||||
$input->getOption('find-exclude') and $options['find']['exclude'] = $input->getOption('find-exclude');
|
||||
$input->getOption('find-contains') and $options['find']['contains'] = $input->getOption('find-contains');
|
||||
$input->getOption('find-not-contains') and $options['find']['not_contains'] = $input->getOption('find-not-contains');
|
||||
$input->getOption('find-path') and $options['find']['path'] = $input->getOption('find-path');
|
||||
$input->getOption('find-sort-by-key') and $options['find']['sort_by']['key'] = $input->getOption('find-sort-by-key');
|
||||
$input->getOption('find-sort-by-direction') and $options['find']['sort_by']['direction'] = $input->getOption('find-sort-by-direction');
|
||||
|
||||
$input->getOption('filter-group-by') and $options['filter']['group_by'] = $input->getOption('filter-group-by');
|
||||
$input->getOption('filter-return') and $options['filter']['return'] = $input->getOption('filter-return');
|
||||
|
||||
if ($input->getOption('filter-where')) {
|
||||
$filterWhere = $input->getOption('filter-where');
|
||||
|
||||
$where = [];
|
||||
|
||||
foreach ($filterWhere as $key => $value) {
|
||||
if (strings($value)->isJson()) {
|
||||
$whereValues = serializers()->json()->decode($value);
|
||||
} else {
|
||||
parse_str($value, $whereValues);
|
||||
}
|
||||
|
||||
$where[] = $whereValues;
|
||||
}
|
||||
|
||||
$options['filter']['where'] = $where;
|
||||
}
|
||||
|
||||
$innerData = [];
|
||||
$innerDataString = '';
|
||||
|
||||
$data = entries()->fetch($id, $options);
|
||||
|
||||
if (count($data) <= 0) {
|
||||
if ($options['collection'] === true) {
|
||||
$output->write(
|
||||
renderToString(
|
||||
div(
|
||||
'Entry [b]' . $id . '[/b] collection is empty.',
|
||||
'color-danger px-2 py-1'
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$output->write(
|
||||
renderToString(
|
||||
div(
|
||||
'Entry [b]' . $id . '[/b] doesn\'t exists.',
|
||||
'color-danger px-2 py-1'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$exportPath = $input->getOption('export-path') ? $input->getOption('export-path') : registry()->get('flextype.settings.entries.export.path');
|
||||
$exportFilename = $input->getOption('export-filename') ? $input->getOption('export-filename') : registry()->get('flextype.settings.entries.export.filename');
|
||||
|
||||
if ($exportFilename == '') {
|
||||
$exportFilename = 'export-' . time();
|
||||
}
|
||||
|
||||
filesystem()->directory($exportPath)->ensureExists(0755, true);
|
||||
filesystem()->file($exportPath . '/' . $exportFilename . '.md')->put(serializers()->frontmatter()->encode($data->toArray()));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
@@ -25,9 +25,11 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
use function array_push;
|
||||
use function count;
|
||||
use function Flextype\collection;
|
||||
use function Flextype\registry;
|
||||
use function Flextype\entries;
|
||||
use function Flextype\serializers;
|
||||
use function Glowy\Strings\strings;
|
||||
use function Glowy\Filesystem\filesystem;
|
||||
use function parse_str;
|
||||
use function Thermage\div;
|
||||
use function Thermage\renderToString;
|
||||
@@ -63,6 +65,10 @@ class EntriesFetchCommand extends Command
|
||||
$this->addOption('filter-sort-by-key', null, InputOption::VALUE_OPTIONAL, 'Sort array collection by key.');
|
||||
$this->addOption('filter-sort-by-direction', null, InputOption::VALUE_OPTIONAL, 'Sort array collection by direction. Order direction: DESC (descending) or ASC (ascending)');
|
||||
$this->addOption('filter-where', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Filters the array collection fields by a given condition.');
|
||||
$this->addOption('export', null, InputOption::VALUE_NONE, 'Export entries.');
|
||||
$this->addOption('export-path', null, InputOption::VALUE_OPTIONAL, 'Export entries.');
|
||||
$this->addOption('export-filename', null, InputOption::VALUE_OPTIONAL, 'Export entries.');
|
||||
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
@@ -132,18 +138,42 @@ class EntriesFetchCommand extends Command
|
||||
$data = entries()->fetch($id, $options);
|
||||
|
||||
if (count($data) <= 0) {
|
||||
$output->write(
|
||||
renderToString(
|
||||
div(
|
||||
'Entry [b]' . $id . '[/b] doesn\'t exists.',
|
||||
'color-danger px-2 py-1'
|
||||
if ($options['collection'] === true) {
|
||||
$output->write(
|
||||
renderToString(
|
||||
div(
|
||||
'Entry [b]' . $id . '[/b] collection is empty.',
|
||||
'color-danger px-2 py-1'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
);
|
||||
} else {
|
||||
$output->write(
|
||||
renderToString(
|
||||
div(
|
||||
'Entry [b]' . $id . '[/b] doesn\'t exists.',
|
||||
'color-danger px-2 py-1'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if ($input->getOption('export')) {
|
||||
$exportPath = $input->getOption('export-path') ? $input->getOption('export-path') : registry()->get('flextype.settings.entries.export.path');
|
||||
$exportFilename = $input->getOption('export-filename') ? $input->getOption('export-filename') : registry()->get('flextype.settings.entries.export.filename');
|
||||
|
||||
if ($exportFilename == '') {
|
||||
$exportFilename = 'export-' . time();
|
||||
}
|
||||
|
||||
filesystem()->directory($exportPath)->ensureExists(0755, true);
|
||||
filesystem()->file($exportPath . '/' . $exportFilename . '.md')->put(serializers()->frontmatter()->encode($data->toArray()));
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
if (isset($options['collection']) && $options['collection'] === true) {
|
||||
foreach ($data->toArray() as $item) {
|
||||
foreach (collection($item)->dot() as $key => $value) {
|
||||
|
@@ -906,6 +906,103 @@ class Entries
|
||||
return FLEXTYPE_PATH_PROJECT . '/' . $this->options['directory'] . '/' . $this->registry()->get('methods.getDirectoryLocation.params.id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Import.
|
||||
*
|
||||
* @param string $file File with data to import.
|
||||
*/
|
||||
public function import(string $file): bool
|
||||
{
|
||||
$extensions = [];
|
||||
foreach (registry()->get('flextype.settings.serializers') as $name => $serializer) {
|
||||
$extensions[$name] = $serializer['extension'];
|
||||
}
|
||||
|
||||
$extension = filesystem()->file($file)->extension();
|
||||
|
||||
if (in_array($extension, $extensions)) {
|
||||
|
||||
$serializers = array_flip($extensions);
|
||||
|
||||
if ($extension == 'php') {
|
||||
$data = serializers()->phparray()->decode($file);
|
||||
} else {
|
||||
$data = serializers()->{$serializers[$extension]}()->decode(filesystem()->file($file)->get());
|
||||
}
|
||||
|
||||
// single
|
||||
if (isset($data['id'])) {
|
||||
$id = $data['id'];
|
||||
$data = collection($data)
|
||||
->set('created_at', date(registry()->get('flextype.settings.date_format'), $data['created_at']))
|
||||
->set('published_at', date(registry()->get('flextype.settings.date_format'), $data['published_at']))
|
||||
->delete(['id', 'modified_at', 'slug'])
|
||||
->toArray();
|
||||
$this->create($id, $data);
|
||||
} else { // collection
|
||||
foreach ($data as $key => $entry) {
|
||||
$id = $entry['id'];
|
||||
$data = collection($entry)
|
||||
->set('created_at', date(registry()->get('flextype.settings.date_format'), $entry['created_at']))
|
||||
->set('published_at', date(registry()->get('flextype.settings.date_format'), $entry['published_at']))
|
||||
->delete(['id', 'modified_at', 'slug'])
|
||||
->toArray();
|
||||
if ($this->has($id)) {
|
||||
$this->update($id, $data);
|
||||
} else {
|
||||
$this->create($id, $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @param string $id Entries ID to fetch data and export to file.
|
||||
* @param array $options Export options.
|
||||
*/
|
||||
public function export(string $id, array $options = []): bool
|
||||
{
|
||||
$path = isset($options['path']) ? $options['path'] : registry()->get('flextype.settings.entries.export.path');
|
||||
$filename = isset($options['filename']) ? $options['filename'] : registry()->get('flextype.settings.entries.export.filename');
|
||||
$serializer = isset($options['serializer']) ? $options['serializer'] : registry()->get('flextype.settings.entries.export.serializer');
|
||||
|
||||
// prepare options
|
||||
$options = collection($options)->delete(['path', 'filename', 'extension', 'serializer'])->toArray();
|
||||
|
||||
// fetch data
|
||||
$data = $this->fetch($id, $options);
|
||||
|
||||
if ($path == '') {
|
||||
$path = FLEXTYPE_ROOT_DIR . '/_export';
|
||||
}
|
||||
|
||||
if ($filename == '') {
|
||||
$filename = 'export-' . time();
|
||||
}
|
||||
|
||||
if (! in_array($serializer, array_keys(registry()->get('flextype.settings.serializers')))) {
|
||||
$serializer = 'json';
|
||||
}
|
||||
|
||||
$extensions = [];
|
||||
foreach (registry()->get('flextype.settings.serializers') as $name => $value) {
|
||||
$extensions[$name] = $value['extension'];
|
||||
}
|
||||
|
||||
// create export directory
|
||||
filesystem()->directory($path)->ensureExists(0755, true);
|
||||
|
||||
// save export
|
||||
return (bool) filesystem()->file($path . '/' . $filename . '.' . $extensions[$serializer])->put(serializers()->{$serializer}()->encode($data->toArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cache ID for entry.
|
||||
*
|
||||
|
@@ -73,7 +73,11 @@ errors:
|
||||
|
||||
# Entries
|
||||
entries:
|
||||
directory: 'entries'
|
||||
directory: "entries"
|
||||
export:
|
||||
path: "_export"
|
||||
filename: ""
|
||||
serializer: "json"
|
||||
cache:
|
||||
string: ""
|
||||
directives:
|
||||
@@ -437,6 +441,7 @@ slugify:
|
||||
# phparray.decode.cache: Cache result data or no. Default is true.
|
||||
serializers:
|
||||
json:
|
||||
extension: "json"
|
||||
decode:
|
||||
cache:
|
||||
enabled: true
|
||||
@@ -448,6 +453,7 @@ serializers:
|
||||
options: 0
|
||||
depth: 512
|
||||
json5:
|
||||
extension: "json5"
|
||||
decode:
|
||||
cache:
|
||||
enabled: true
|
||||
@@ -459,6 +465,7 @@ serializers:
|
||||
options: 0
|
||||
depth: 512
|
||||
yaml:
|
||||
extension: "yaml"
|
||||
decode:
|
||||
cache:
|
||||
enabled: true
|
||||
@@ -470,6 +477,7 @@ serializers:
|
||||
indent: 2
|
||||
flags: 0
|
||||
frontmatter:
|
||||
extension: "md"
|
||||
decode:
|
||||
cache:
|
||||
enabled: true
|
||||
@@ -483,6 +491,7 @@ serializers:
|
||||
serializer: yaml
|
||||
allowed: ['yaml', 'json', 'json5', 'neon']
|
||||
neon:
|
||||
extension: "neon"
|
||||
decode:
|
||||
cache:
|
||||
enabled: true
|
||||
@@ -491,6 +500,7 @@ serializers:
|
||||
blockMode: false
|
||||
indentation: "\t"
|
||||
phparray:
|
||||
extension: "php"
|
||||
decode:
|
||||
cache:
|
||||
enabled: true
|
||||
|
Reference in New Issue
Block a user