Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added new console commands to import or export formie form to a target folder #1972

Open
wants to merge 5 commits into
base: craft-5
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/get-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ return [
### Theme
- `themeConfig` - Sets the configuration for theming your form and fields. See below for an example.

### Export
- `defaultExportFolder` - Sets the default folder for exported forms via console commands.

## Control Panel

You can also manage configuration settings through the Control Panel by visiting Settings → Formie.
Expand Down
230 changes: 225 additions & 5 deletions src/console/controllers/FormsController.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
<?php

namespace verbb\formie\console\controllers;

use verbb\formie\elements\Form;
use verbb\formie\helpers\ImportExportHelper;
use verbb\formie\jobs\ImportForm;
use verbb\formie\Formie;

use Craft;
use craft\console\Controller;
use craft\helpers\Db;
use craft\helpers\Console;
use craft\helpers\Json;
use craft\helpers\Queue;
use craft\helpers\FileHelper;

use Throwable;

Expand All @@ -20,9 +27,20 @@ class FormsController extends Controller
// Properties
// =========================================================================

/**
* @var string form ID as a comma-separated list
*/
public ?string $formId = null;

/**
* @var string form handle as a comma-separated list
*/
public ?string $formHandle = null;

/**
* @var bool Create a new form, prevent updating an existing form
*/
public bool $create = false;

// Public Methods
// =========================================================================
Expand All @@ -31,9 +49,15 @@ public function options($actionID): array
{
$options = parent::options($actionID);

if ($actionID === 'delete') {
$options[] = 'formId';
$options[] = 'formHandle';
switch ($actionID) {
case 'delete':
$options[] = 'formId';
$options[] = 'formHandle';
break;
case 'import':
case 'import-all':
$options[] = 'create';
break;
}

return $options;
Expand Down Expand Up @@ -80,17 +104,213 @@ public function actionDelete(): int
}

$elementsText = $count === 1 ? 'form' : 'forms';
$this->stdout("Deleting {$count} {$elementsText} for form #{$formId} ..." . PHP_EOL, Console::FG_YELLOW);
$this->stdout("Deleting $count $elementsText for form $formId ..." . PHP_EOL, Console::FG_YELLOW);

$elementsService = Craft::$app->getElements();

foreach (Db::each($query) as $element) {
$elementsService->deleteElement($element);

$this->stdout("Deleted form #{$element->id} ..." . PHP_EOL, Console::FG_GREEN);
$this->stdout("Deleted form $element->id ..." . PHP_EOL, Console::FG_GREEN);
}
}

return ExitCode::OK;
}

/**
* List all possible Formie forms to be exported or imported.
*/
public function actionList($folderPath = null): int
{
$path = $folderPath ?? $this->getExportPath();
try {
$files = FileHelper::findFiles($path, ['only' => ['*.json']]);
} catch (\Throwable $th) {
$this->stderr("The export directory is empty or does not exist." . PHP_EOL, Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}

if (!empty($files)) {
$listEntries[] = [
'title' => 'JSON to import:',
'entriesList' => array_map(function ($file) {
return [
'name' => $file,
'title' => ''
];
}, $files)
];
}

$allForms = Formie::$plugin->getForms()->getAllForms();
if (!empty($allForms)) {
$listEntries[] = [
'title' => 'Formie forms:',
'entriesList' => array_map(function ($form) {
return [
'name' => "$form->id: $form->handle",
'title' => $form->title
];
}, $allForms)
];
}

foreach ($listEntries as $entries) {
$this->stdout($entries['title'] . PHP_EOL, Console::FG_YELLOW);

$handleMaxLen = max(array_map('strlen', array_column($entries['entriesList'], 'name')));

foreach ($entries['entriesList'] as $entry) {
$this->stdout("- " . $entry['name'], Console::FG_GREEN);
$this->stdout(Console::moveCursorTo($handleMaxLen + 5));
$this->stdout($entry['title'] . PHP_EOL);
}
}


return ExitCode::OK;
}

/**
* Export Formie forms as JSON. Accepts comma-separated lists of form IDs and/or handles.
*/
public function actionExport($idsOrHandles = null): int
{
$formIds = null;

foreach (explode(',', $idsOrHandles) as $idOrHandle) {
if (is_numeric($idOrHandle)) {
$formIds[] = $idOrHandle;
} else {
$formIds[] = Form::find()->handle($idOrHandle)->one()->id ?? null;
}
}

if (!$formIds) {
$this->stderr('Unable to find any matching forms.' . PHP_EOL, Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}

$query = Form::find()->id($formIds);
$count = (int)$query->count();

if ($count === 0) {
$this->stdout('No forms exist for that criteria.' . PHP_EOL, Console::FG_YELLOW);
}

$elementsText = $count === 1 ? 'form' : 'forms';
$this->stdout("Exporting $count $elementsText ..." . PHP_EOL, Console::FG_YELLOW);

foreach (Db::each($query) as $element) {
try {
$formExport = ImportExportHelper::generateFormExport($element);
$json = Json::encode($formExport, JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK);
$exportPath = $this->generateExportPathByHandle($element->handle);
FileHelper::writeToFile($exportPath, $json);
$this->stdout("Exporting form $element->id to $exportPath." . PHP_EOL, Console::FG_GREEN);
} catch (Throwable $e) {

$this->stderr("Unable to export form $element->id." . PHP_EOL, Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
}

return ExitCode::OK;
}

/**
* Import a Formie form JSON from a path.
*/
public function actionImport($fileLocation = null): int
{
if ($fileLocation === null) {
$this->stderr('You must provide a path to a JSON file.' . PHP_EOL, Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}

if (!is_file($fileLocation)) {
$this->stderr("No file exists at the given path." . PHP_EOL, Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}

if (strtolower(pathinfo($fileLocation, PATHINFO_EXTENSION)) !== 'json') {
$this->stderr("The file is not of type JSON." . PHP_EOL, Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}

try {
$json = Json::decode(file_get_contents($fileLocation));
} catch (\Exception $e) {
$this->stderr("Failed to decode JSON from the file." . PHP_EOL, Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}

// default, update existing form
$formAction = $this->create ? 'create' : 'update';

$form = ImportExportHelper::importFormFromJson($json, $formAction);

// check for errors
if ($form->getConsolidatedErrors()) {
$this->stderr("Unable to import the form." . PHP_EOL, Console::FG_RED);
$errors = Json::encode($form->getConsolidatedErrors());
$this->stderr("Errors: $errors" . PHP_EOL, Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}

$this->stdout("Form $form->handle has be {$formAction}d." . PHP_EOL, Console::FG_GREEN);

return ExitCode::OK;
}

/**
* Import all Formie JSON from a folder.
*/
public function actionImportAll($folderPath = null): int
{
$path = $folderPath ?? $this->getExportPath();
try {
$files = FileHelper::findFiles($path, ['only' => ['*.json']]);
} catch (\Throwable $th) {
$this->stderr("The export directory is empty or does not exist." . PHP_EOL, Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}

if (empty($files)) {
$this->stderr("No JSON files found in folder $path." . PHP_EOL, Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}

// use jobs to prevent db overload or php timeout
foreach ($files as $file) {

Queue::push(new ImportForm(
[
'fileLocation' => $file,
'formAction' => $this->create ? 'create' : 'update'
]
));

$basename = basename($file);
$this->stdout("File '$basename' has been added to the import queue." . PHP_EOL, Console::FG_GREEN);
}


return ExitCode::OK;
}

// Protected Methods
// =========================================================================

private function generateExportPathByHandle($handle): string
{
return $this->getExportPath() . DIRECTORY_SEPARATOR . "formie-$handle.json";
}

private function getExportPath(): string
{
$settings = Formie::$plugin->getSettings();
return $settings->getAbsoluteDefaultExportFolder();
}
}
44 changes: 5 additions & 39 deletions src/controllers/ImportExportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,46 +149,12 @@ public function actionImportComplete(): ?Response
}

$json = Json::decode(file_get_contents($fileLocation));


$form = ImportExportHelper::importFormFromJson($json, $formAction);

// Find an existing form with the same handle
$existingForm = null;
$formHandle = $json['handle'] ?? null;

if ($formHandle) {
$existingForm = Formie::$plugin->getForms()->getFormByHandle($formHandle);
}

// When creating a new form, change the handle
if ($formAction === 'create') {
$formHandles = (new Query())
->select(['handle'])
->from(Table::FORMIE_FORMS)
->column();

$json['handle'] = HandleHelper::getUniqueHandle($formHandles, $json['handle']);
}

if ($formAction === 'update') {
// Update the form (force)
$form = ImportExportHelper::createFormFromImport($json, $existingForm);
} else {
// Create the form element, ready to go
$form = ImportExportHelper::createFormFromImport($json);
}

// Because we also export the UID for forms, we need to check if we're importing a new form, but we've
// found a form with the same UID. If this happens, then the original form will be overwritten
if ($formAction === 'create') {
// Is there already a form that exists with this UID? Then we need to assign a new one.
// See discussion https://github.com/verbb/formie/discussions/1696 and actual issue https://github.com/verbb/formie/issues/1725
$existingForm = Formie::$plugin->getForms()->getFormByHandle($form->handle);

if ($existingForm) {
$form->uid = StringHelper::UUID();
}
}

if (!Craft::$app->getElements()->saveElement($form)) {
// check for errors
if( $form->getConsolidatedErrors() ){
$this->setFailFlash(Craft::t('formie', 'Unable to import form.'));

Craft::$app->getUrlManager()->setRouteParams([
Expand Down
Loading