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 @@ -34,6 +34,7 @@
'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => $baseDir . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php',
'OCA\\WorkflowEngine\\Migration\\Version2000Date20190808074233' => $baseDir . '/../lib/Migration/Version2000Date20190808074233.php',
'OCA\\WorkflowEngine\\Migration\\Version2200Date20210805101925' => $baseDir . '/../lib/Migration/Version2200Date20210805101925.php',
'OCA\\WorkflowEngine\\Migration\\Version3000Date20260531150003' => $baseDir . '/../lib/Migration/Version3000Date20260531150003.php',
'OCA\\WorkflowEngine\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
'OCA\\WorkflowEngine\\Service\\Logger' => $baseDir . '/../lib/Service/Logger.php',
'OCA\\WorkflowEngine\\Service\\RuleMatcher' => $baseDir . '/../lib/Service/RuleMatcher.php',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class ComposerStaticInitWorkflowEngine
'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => __DIR__ . '/..' . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php',
'OCA\\WorkflowEngine\\Migration\\Version2000Date20190808074233' => __DIR__ . '/..' . '/../lib/Migration/Version2000Date20190808074233.php',
'OCA\\WorkflowEngine\\Migration\\Version2200Date20210805101925' => __DIR__ . '/..' . '/../lib/Migration/Version2200Date20210805101925.php',
'OCA\\WorkflowEngine\\Migration\\Version3000Date20260531150003' => __DIR__ . '/..' . '/../lib/Migration/Version3000Date20260531150003.php',
'OCA\\WorkflowEngine\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
'OCA\\WorkflowEngine\\Service\\Logger' => __DIR__ . '/..' . '/../lib/Service/Logger.php',
'OCA\\WorkflowEngine\\Service\\RuleMatcher' => __DIR__ . '/..' . '/../lib/Service/RuleMatcher.php',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public function show(string $id): DataResponse {
* @param string $operation Operation class to execute on match
* @param string $entity The matched entity
* @param list<class-string<IEntityEvent>> $events The list of events on which the rule should be validated
* @param string $description Optional free-text description of the workflow rule
* @return DataResponse<Http::STATUS_OK, WorkflowEngineRule, array{}>
*
* 200: Workflow created
Expand All @@ -109,10 +110,11 @@ public function create(
string $operation,
string $entity,
array $events,
string $description = '',
): DataResponse {
$context = $this->getScopeContext();
try {
$operation = $this->manager->addOperation($class, $name, $checks, $operation, $context, $entity, $events);
$operation = $this->manager->addOperation($class, $name, $checks, $operation, $context, $entity, $events, $description);
$operation = $this->manager->formatOperation($operation);
return new DataResponse($operation);
} catch (\UnexpectedValueException $e) {
Expand All @@ -134,6 +136,7 @@ public function create(
* @param string $operation Operation action to execute on match
* @param string $entity The matched entity
* @param list<class-string<IEntityEvent>> $events The list of events on which the rule should be validated
* @param string $description Optional free-text description of the workflow rule
* @return DataResponse<Http::STATUS_OK, WorkflowEngineRule, array{}>
*
* 200: Workflow updated
Expand All @@ -149,10 +152,11 @@ public function update(
string $operation,
string $entity,
array $events,
string $description = '',
): DataResponse {
try {
$context = $this->getScopeContext();
$operation = $this->manager->updateOperation($id, $name, $checks, $operation, $context, $entity, $events);
$operation = $this->manager->updateOperation($id, $name, $checks, $operation, $context, $entity, $events, $description);
$operation = $this->manager->formatOperation($operation);
return new DataResponse($operation);
} catch (\UnexpectedValueException $e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public function show(string $id): DataResponse {
* @param string $operation Operation class to execute on match
* @param string $entity The matched entity
* @param list<class-string<IEntityEvent>> $events The list of events on which the rule should be validated
* @param string $description Optional free-text description of the workflow rule
* @return DataResponse<Http::STATUS_OK, WorkflowEngineRule, array{}>
*
* 200: Workflow created
Expand All @@ -74,8 +75,8 @@ public function show(string $id): DataResponse {
#[\Override]
#[PasswordConfirmationRequired]
#[ApiRoute(verb: 'POST', url: '/api/v1/workflows/global')]
public function create(string $class, string $name, array $checks, string $operation, string $entity, array $events): DataResponse {
return parent::create($class, $name, $checks, $operation, $entity, $events);
public function create(string $class, string $name, array $checks, string $operation, string $entity, array $events, string $description = ''): DataResponse {
return parent::create($class, $name, $checks, $operation, $entity, $events, $description);
}

/**
Expand All @@ -87,6 +88,7 @@ public function create(string $class, string $name, array $checks, string $opera
* @param string $operation Operation action to execute on match
* @param string $entity The matched entity
* @param list<class-string<IEntityEvent>> $events The list of events on which the rule should be validated
* @param string $description Optional free-text description of the workflow rule
* @return DataResponse<Http::STATUS_OK, WorkflowEngineRule, array{}>
*
* 200: Workflow updated
Expand All @@ -97,8 +99,8 @@ public function create(string $class, string $name, array $checks, string $opera
#[\Override]
#[PasswordConfirmationRequired]
#[ApiRoute(verb: 'PUT', url: '/api/v1/workflows/global/{id}')]
public function update(int $id, string $name, array $checks, string $operation, string $entity, array $events): DataResponse {
return parent::update($id, $name, $checks, $operation, $entity, $events);
public function update(int $id, string $name, array $checks, string $operation, string $entity, array $events, string $description = ''): DataResponse {
return parent::update($id, $name, $checks, $operation, $entity, $events, $description);
}

/**
Expand Down
10 changes: 6 additions & 4 deletions apps/workflowengine/lib/Controller/UserWorkflowsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public function show(string $id): DataResponse {
* @param string $operation Operation class to execute on match
* @param string $entity The matched entity
* @param list<class-string<IEntityEvent>> $events The list of events on which the rule should be validated
* @param string $description Optional free-text description of the workflow rule
* @return DataResponse<Http::STATUS_OK, WorkflowEngineRule, array{}>
*
* 200: Workflow created
Expand All @@ -93,8 +94,8 @@ public function show(string $id): DataResponse {
#[NoAdminRequired]
#[PasswordConfirmationRequired]
#[ApiRoute(verb: 'POST', url: '/api/v1/workflows/user')]
public function create(string $class, string $name, array $checks, string $operation, string $entity, array $events): DataResponse {
return parent::create($class, $name, $checks, $operation, $entity, $events);
public function create(string $class, string $name, array $checks, string $operation, string $entity, array $events, string $description = ''): DataResponse {
return parent::create($class, $name, $checks, $operation, $entity, $events, $description);
}

/**
Expand All @@ -106,6 +107,7 @@ public function create(string $class, string $name, array $checks, string $opera
* @param string $operation Operation action to execute on match
* @param string $entity The matched entity
* @param list<class-string<IEntityEvent>> $events The list of events on which the rule should be validated
* @param string $description Optional free-text description of the workflow rule
* @return DataResponse<Http::STATUS_OK, WorkflowEngineRule, array{}>
*
* 200: Workflow updated
Expand All @@ -117,8 +119,8 @@ public function create(string $class, string $name, array $checks, string $opera
#[NoAdminRequired]
#[PasswordConfirmationRequired]
#[ApiRoute(verb: 'PUT', url: '/api/v1/workflows/user/{id}')]
public function update(int $id, string $name, array $checks, string $operation, string $entity, array $events): DataResponse {
return parent::update($id, $name, $checks, $operation, $entity, $events);
public function update(int $id, string $name, array $checks, string $operation, string $entity, array $events, string $description = ''): DataResponse {
return parent::update($id, $name, $checks, $operation, $entity, $events, $description);
}

/**
Expand Down
26 changes: 21 additions & 5 deletions apps/workflowengine/lib/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
* @psalm-import-type WorkflowEngineRule from ResponseDefinitions
*/
class Manager implements IManager {
public const MAX_NAME_BYTES = 256;

/** @var array<string, array<string, array<int, WorkflowEngineCheck>>> */
protected array $operations = [];

Expand Down Expand Up @@ -320,12 +322,14 @@
string $operation,
string $entity,
array $events,
string $description = '',
): int {
$query = $this->connection->getQueryBuilder();
$query->insert('flow_operations')
->values([
'class' => $query->createNamedParameter($class),
'name' => $query->createNamedParameter($name),
'description' => $query->createNamedParameter($description),
'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))),
'operation' => $query->createNamedParameter($operation),
'entity' => $query->createNamedParameter($entity),
Expand Down Expand Up @@ -452,6 +456,7 @@
* @param string $name
* @param list<WorkflowEngineCheck> $checks
* @param string $operation
* @param string $description optional free-text description shown in the UI
* @return array The added operation
* @throws \UnexpectedValueException
* @throws Exception
Expand All @@ -464,8 +469,9 @@
ScopeContext $scope,
string $entity,
array $events,
string $description = '',
) {
$this->validateOperation($class, $name, $checks, $operation, $scope, $entity, $events);
$this->validateOperation($class, $name, $checks, $operation, $scope, $entity, $events, $description);

$this->connection->beginTransaction();

Expand All @@ -475,7 +481,7 @@
$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
}

$id = $this->insertOperation($class, $name, $checkIds, $operation, $entity, $events);
$id = $this->insertOperation($class, $name, $checkIds, $operation, $entity, $events, $description);
$this->addScope($id, $scope);

$this->connection->commit();
Expand Down Expand Up @@ -520,6 +526,7 @@
* @param string $name
* @param list<WorkflowEngineCheck> $checks
* @param string $operation
* @param string $description optional free-text description shown in the UI
* @return array The updated operation
* @throws \UnexpectedValueException
* @throws \DomainException
Expand All @@ -533,12 +540,13 @@
ScopeContext $scopeContext,
string $entity,
array $events,
string $description = '',
): array {
if (!$this->canModify($id, $scopeContext)) {
throw new \DomainException('Target operation not within scope');
};
$row = $this->getOperation($id);
$this->validateOperation($row['class'], $name, $checks, $operation, $scopeContext, $entity, $events);
$this->validateOperation($row['class'], $name, $checks, $operation, $scopeContext, $entity, $events, $description);

$checkIds = [];
try {
Expand All @@ -550,6 +558,7 @@
$query = $this->connection->getQueryBuilder();
$query->update('flow_operations')
->set('name', $query->createNamedParameter($name))
->set('description', $query->createNamedParameter($description))
->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds))))
->set('operation', $query->createNamedParameter($operation))
->set('entity', $query->createNamedParameter($entity))
Expand Down Expand Up @@ -609,7 +618,7 @@
*/
protected function validateEvents(string $entity, array $events, IOperation $operation): void {
/** @psalm-suppress TaintedCallable newInstance is not called */
$reflection = new \ReflectionClass($entity);

Check failure on line 621 in apps/workflowengine/lib/Manager.php

View workflow job for this annotation

GitHub Actions / static-code-analysis-security

TaintedCallable

apps/workflowengine/lib/Manager.php:621:38: TaintedCallable: Detected tainted text (see https://psalm.dev/243)
if ($entity !== IEntity::class && !in_array(IEntity::class, $reflection->getInterfaceNames())) {
throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity]));
}
Expand Down Expand Up @@ -645,13 +654,17 @@
* @param array $events
* @throws \UnexpectedValueException
*/
public function validateOperation(string $class, string $name, array $checks, string $operation, ScopeContext $scope, string $entity, array $events): void {
public function validateOperation(string $class, string $name, array $checks, string $operation, ScopeContext $scope, string $entity, array $events, string $description = ''): void {
if (strlen($operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
throw new \UnexpectedValueException($this->l->t('The provided operation data is too long'));
}

if (strlen($name) > self::MAX_NAME_BYTES) {
throw new \UnexpectedValueException($this->l->t('The provided name is too long'));
}

/** @psalm-suppress TaintedCallable newInstance is not called */
$reflection = new \ReflectionClass($class);

Check failure on line 667 in apps/workflowengine/lib/Manager.php

View workflow job for this annotation

GitHub Actions / static-code-analysis-security

TaintedCallable

apps/workflowengine/lib/Manager.php:667:38: TaintedCallable: Detected tainted text (see https://psalm.dev/243)
if ($class !== IOperation::class && !in_array(IOperation::class, $reflection->getInterfaceNames())) {
throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]) . join(', ', $reflection->getInterfaceNames()));
}
Expand Down Expand Up @@ -684,7 +697,7 @@
throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
}

$reflection = new \ReflectionClass($check['class']);

Check failure on line 700 in apps/workflowengine/lib/Manager.php

View workflow job for this annotation

GitHub Actions / static-code-analysis-security

TaintedCallable

apps/workflowengine/lib/Manager.php:700:39: TaintedCallable: Detected tainted text (see https://psalm.dev/243)
if ($check['class'] !== ICheck::class && !in_array(ICheck::class, $reflection->getInterfaceNames())) {
throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
}
Expand Down Expand Up @@ -810,7 +823,7 @@
}

/**
* @param array{class: class-string<\OCP\WorkflowEngine\IOperation>, entity: class-string<\OCP\WorkflowEngine\IEntity>, checks: string, events: string, id: int, name: string, operation: string} $operation
* @param array{class: class-string<\OCP\WorkflowEngine\IOperation>, entity: class-string<\OCP\WorkflowEngine\IEntity>, checks: string, events: string, id: int, name: string, description: ?string, operation: string} $operation
* @return WorkflowEngineRule
*/
public function formatOperation(array $operation): array {
Expand All @@ -823,6 +836,9 @@
$events = json_decode($operation['events'], true) ?? [];
$operation['events'] = $events;

$operation['name'] = (string)($operation['name'] ?? '');
$operation['description'] = (string)($operation['description'] ?? '');

return $operation;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\WorkflowEngine\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
use Override;

/**
* Adds an optional description column to flow_operations
*/
class Version3000Date20260531150003 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
#[Override]
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
$schema = $schemaClosure();

if (!$schema->hasTable('flow_operations')) {
return null;
}

$table = $schema->getTable('flow_operations');
if (!$table->hasColumn('description')) {
$table->addColumn('description', Types::TEXT, [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the description is limited in length, shouldn't it be a Types::STRING with a 'length' => 4000 like the name column?

@letmefixthiscode letmefixthiscode Jun 5, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

look here:

// Adjust STRING columns with a length higher than 4000 to TEXT (clob)
>4000 is converted to text (so yeah i have 4000 not >4000 - so should i use Types::STRING or remove the 4000 limit?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @Altahrim has an idea of what's the best solution

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A long description sounds okay and I don't see why we would have to strictly limit it?

'notnull' => false,
'default' => null,
]);
}

return $schema;
}
}
1 change: 1 addition & 0 deletions apps/workflowengine/lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* id: int,
* class: class-string<IOperation>,
* name: string,
* description: string,
* checks: list<WorkflowEngineCheck>,
* operation: string,
* entity: class-string<IEntity>,
Expand Down
14 changes: 14 additions & 0 deletions apps/workflowengine/openapi-administration.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"id",
"class",
"name",
"description",
"checks",
"operation",
"entity",
Expand All @@ -106,6 +107,9 @@
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"checks": {
"type": "array",
"items": {
Expand Down Expand Up @@ -315,6 +319,11 @@
"type": "string",
"minLength": 1
}
},
"description": {
"type": "string",
"default": "",
"description": "Optional free-text description of the workflow rule"
}
}
}
Expand Down Expand Up @@ -640,6 +649,11 @@
"type": "string",
"minLength": 1
}
},
"description": {
"type": "string",
"default": "",
"description": "Optional free-text description of the workflow rule"
}
}
}
Expand Down
Loading
Loading