Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,15 @@ public function index(Request $request)
*/
public function store(Request $request)
{
$request->validate(EnvironmentVariable::rules(), EnvironmentVariable::messages());
$environment_variable = EnvironmentVariable::create($request->all());
$data = $this->prepareAssetLinkInput($request->all());
validator($data, EnvironmentVariable::rules(), EnvironmentVariable::messages())->validate();
$linkedAsset = EnvironmentVariable::validateAssetLinkConsistency($data);
if ($linkedAsset) {
$data['value'] = (string) $linkedAsset->id;
}
$environment_variable = EnvironmentVariable::create($data);
// Register the Event
EnvironmentVariablesCreated::dispatch($request->all());
EnvironmentVariablesCreated::dispatch($data);

return new EnvironmentVariableResource($environment_variable);
}
Expand Down Expand Up @@ -172,14 +177,22 @@ public function show(EnvironmentVariable $environment_variable)
*/
public function update(EnvironmentVariable $environment_variable, Request $request)
{
$fields = ['name', 'description'];
if ($request->filled('value')) {
$data = $this->prepareAssetLinkInput($request->all());
// Validate the request, passing in the existing variable to tweak unique rule on name
validator($data, EnvironmentVariable::rules($environment_variable), EnvironmentVariable::messages())->validate();
$linkedAsset = EnvironmentVariable::validateAssetLinkConsistency($data);

$fields = ['name', 'description', 'asset_type', 'do_not_update'];
if ($linkedAsset) {
// Guarantee value is always the selected asset's ID on this instance.
$data['value'] = (string) $linkedAsset->id;
$fields[] = 'value';
} elseif (array_key_exists('value', $data) && $data['value'] !== null && $data['value'] !== '') {
$fields[] = 'value';
}
// Validate the request, passing in the existing variable to tweak unique rule on name
$request->validate(EnvironmentVariable::rules($environment_variable));

$original = $environment_variable->getOriginal();
$environment_variable->fill($request->only($fields));
$environment_variable->fill(collect($data)->only($fields)->all());
$environment_variable->save();

$changes = $environment_variable->getChanges();
Expand All @@ -190,6 +203,18 @@ public function update(EnvironmentVariable $environment_variable, Request $reque
return new EnvironmentVariableResource($environment_variable);
}

/**
* Normalize empty asset_type to null.
*/
private function prepareAssetLinkInput(array $data): array
{
if (array_key_exists('asset_type', $data) && $data['asset_type'] === '') {
$data['asset_type'] = null;
}

return $data;
}

/**
* @OA\Delete(
* path="/environment_variables/{environment_variable_id}",
Expand Down
2 changes: 2 additions & 0 deletions ProcessMaker/ImportExport/DependentType.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ abstract class DependentType

const ENVIRONMENT_VARIABLE_VALUE = 'environment_variables_value';

const ENVIRONMENT_VARIABLE_ASSET = 'environment_variable_asset';

const SCRIPT_EXECUTORS = 'script_executors';

const SUB_PROCESSES = 'sub_processes';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

namespace ProcessMaker\ImportExport\Exporters;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use ProcessMaker\Enums\ExporterMap;
use ProcessMaker\ImportExport\DependentType;
use ProcessMaker\ImportExport\Manifest;
use ProcessMaker\ImportExport\Options;
use ProcessMaker\ImportExport\Psudomodels\Psudomodel;

class EnvironmentVariableExporter extends ExporterBase
{
Expand All @@ -14,13 +20,75 @@ class EnvironmentVariableExporter extends ExporterBase

public $incrementStringSeparator = '_';

public function __construct(
Model|Psudomodel|null $model,
Manifest $manifest,
Options $options,
$ignoreExplicitDiscard
) {
parent::__construct($model, $manifest, $options, $ignoreExplicitDiscard);

// PHP does not allow closures as property defaults.
$this->discard = fn ($envVar) => (bool) ($envVar->do_not_update ?? false);
}

public function export() : void
{
$this->addReference(DependentType::ENVIRONMENT_VARIABLE_VALUE, $this->model->value);

if (!$this->model->hasLinkedAsset()) {
return;
}

$asset = $this->model->resolveLinkedAsset();
if (!$asset) {
Log::warning('EnvironmentVariableExporter: linked asset not found during export', [
'environment_variable' => $this->model->name,
'asset_type' => $this->model->asset_type,
'value' => $this->model->value,
]);

return;
}

$exporterClass = ExporterMap::getExporterClassForModel($asset);
if (!$exporterClass) {
Log::warning('EnvironmentVariableExporter: no exporter registered for linked asset', [
'environment_variable' => $this->model->name,
'asset_type' => $this->model->asset_type,
]);

return;
}

$this->addDependent(DependentType::ENVIRONMENT_VARIABLE_ASSET, $asset, $exporterClass);
}

public function import() : bool
{
foreach ($this->getDependents(DependentType::ENVIRONMENT_VARIABLE_ASSET, true) as $dependent) {
$asset = $dependent->model;
if ($asset && $asset->exists) {
$this->model->asset_type = get_class($asset);
$this->model->value = (string) $asset->id;

return $this->model->save();
}
}

if ($this->model->asset_type) {
// Linked in source, but asset was not imported and was not found on target.
$this->logger?->addWarning(__(
'Asset linked to environment variable ":env_variable" was missing on import; link and value were cleared',
['env_variable' => $this->model->name]
));
$this->model->asset_type = null;
$this->model->value = '';

return $this->model->save();
}

// Standard case (non-linked) env var: restore secret/value from export.
$this->model->value = $this->getReference(DependentType::ENVIRONMENT_VARIABLE_VALUE);

return $this->model->save();
Expand Down
44 changes: 42 additions & 2 deletions ProcessMaker/ImportExport/Exporters/ExporterBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ abstract class ExporterBase implements ExporterInterface

public $hidden = false;

/**
* When true (or a callable returning true), this asset is explicitly discarded.
* Callables receive the model: fn (Model $model): bool
*
* @var bool|callable
*/
public $discard = false;

public static $forceUpdate = false;
Expand Down Expand Up @@ -135,7 +141,7 @@ public function uuid() : string

public function include() : bool
{
if ($this->discard) { // Explicit Discard (not from passed-in options)
if ($this->isExplicitlyDiscarded()) { // Explicit Discard (not from passed-in options)
if (!$this->ignoreExplicitDiscard) {
return false;
}
Expand All @@ -148,6 +154,40 @@ public function include() : bool
return true;
}

/**
* Resolve $discard whether it is a boolean or a callable checked against the model.
*/
public function isExplicitlyDiscarded(): bool
{
if (is_callable($this->discard)) {
return (bool) ($this->discard)($this->model);
}

return (bool) $this->discard;
}

/**
* Import-time check: should an existing target model be discarded (not overwritten)?
*
* Only callables are evaluated here. Boolean `$discard = true` (Users/Groups) is an
* export-time flag handled by include(), not an import-time skip.
* Psudomodels (e.g. Signal) are never discarded by this path.
*/
public static function shouldDiscardExistingModelDuringImport(Model|Psudomodel|null $model): bool
{
if (!$model instanceof Model || !$model->exists) {
return false;
}

$exporter = new static($model, new Manifest(), new Options([]), false);

if (!is_callable($exporter->discard)) {
return false;
}

return (bool) ($exporter->discard)($model);
}

public function addDependent(string $type, Model|Psudomodel $dependentModel, string $exporterClass, $meta = null)
{
$uuid = $dependentModel->uuid;
Expand Down Expand Up @@ -304,7 +344,7 @@ public function toArray()
'hidden' => $this->hidden,
'mode' => $this->mode,
'saveAssetsMode' => $this->saveAssetsMode,
'explicit_discard' => $this->discard,
'explicit_discard' => $this->isExplicitlyDiscarded(),
'dependents' => array_map(fn ($d) => $d->toArray(), $this->dependents),
'name' => $this->getName($this->model),
'description' => $this->getDescription(),
Expand Down
5 changes: 5 additions & 0 deletions ProcessMaker/ImportExport/Manifest.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ public static function getModel($uuid, $assetInfo, $mode, $exporterClass, $isRoo
$mode = 'discard';
}

// Per-asset explicit discard (e.g. env vars with do_not_update on the target).
if ($model && $exporterClass::shouldDiscardExistingModelDuringImport($model)) {
$mode = 'discard';
}

if ($exporterClass::$forceUpdate) {
$mode = 'update';
}
Expand Down
Loading
Loading