diff --git a/src/flextype/core/Console/Commands/Entries/EntriesExportCommand.php b/src/flextype/core/Console/Commands/Entries/EntriesExportCommand.php new file mode 100644 index 00000000..c76446a6 --- /dev/null +++ b/src/flextype/core/Console/Commands/Entries/EntriesExportCommand.php @@ -0,0 +1,176 @@ +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; + } +} diff --git a/src/flextype/core/Console/Commands/Entries/EntriesFetchCommand.php b/src/flextype/core/Console/Commands/Entries/EntriesFetchCommand.php index bed0a275..74d12a1e 100644 --- a/src/flextype/core/Console/Commands/Entries/EntriesFetchCommand.php +++ b/src/flextype/core/Console/Commands/Entries/EntriesFetchCommand.php @@ -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) { diff --git a/src/flextype/core/Entries/Entries.php b/src/flextype/core/Entries/Entries.php index 2b78e181..27262e04 100755 --- a/src/flextype/core/Entries/Entries.php +++ b/src/flextype/core/Entries/Entries.php @@ -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. * diff --git a/src/flextype/settings.yaml b/src/flextype/settings.yaml index e97eca38..b92f8db5 100644 --- a/src/flextype/settings.yaml +++ b/src/flextype/settings.yaml @@ -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