From d015cd9c55b9e5b1e7899fbf7ae79fb5ab4c0beb Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 7 Aug 2019 11:01:09 +0200 Subject: [PATCH 01/68] provides an OCS workflow controller for admins Signed-off-by: Arthur Schiwon --- apps/workflowengine/appinfo/routes.php | 5 +- .../composer/composer/autoload_classmap.php | 1 + .../composer/composer/autoload_static.php | 1 + .../Controller/GlobalWorkflowsController.php | 111 ++++++++++++++++++ apps/workflowengine/lib/Manager.php | 35 ++++++ 5 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 apps/workflowengine/lib/Controller/GlobalWorkflowsController.php diff --git a/apps/workflowengine/appinfo/routes.php b/apps/workflowengine/appinfo/routes.php index 5ae74bcafc3..c650bee4cf2 100644 --- a/apps/workflowengine/appinfo/routes.php +++ b/apps/workflowengine/appinfo/routes.php @@ -26,5 +26,8 @@ return [ ['name' => 'flowOperations#updateOperation', 'url' => '/operations/{id}', 'verb' => 'PUT'], ['name' => 'flowOperations#deleteOperation', 'url' => '/operations/{id}', 'verb' => 'DELETE'], ['name' => 'requestTime#getTimezones', 'url' => '/timezones', 'verb' => 'GET'], - ] + ], + 'ocs-resources' => [ + 'global_workflows' => ['url' => '/api/v1/workflows/global'], + ], ]; diff --git a/apps/workflowengine/composer/composer/autoload_classmap.php b/apps/workflowengine/composer/composer/autoload_classmap.php index 583213eb853..7d73b3a6241 100644 --- a/apps/workflowengine/composer/composer/autoload_classmap.php +++ b/apps/workflowengine/composer/composer/autoload_classmap.php @@ -18,6 +18,7 @@ return array( 'OCA\\WorkflowEngine\\Check\\RequestUserAgent' => $baseDir . '/../lib/Check/RequestUserAgent.php', 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => $baseDir . '/../lib/Check/UserGroupMembership.php', 'OCA\\WorkflowEngine\\Controller\\FlowOperations' => $baseDir . '/../lib/Controller/FlowOperations.php', + 'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => $baseDir . '/../lib/Controller/GlobalWorkflowsController.php', 'OCA\\WorkflowEngine\\Controller\\RequestTime' => $baseDir . '/../lib/Controller/RequestTime.php', 'OCA\\WorkflowEngine\\Manager' => $baseDir . '/../lib/Manager.php', 'OCA\\WorkflowEngine\\Settings\\Section' => $baseDir . '/../lib/Settings/Section.php', diff --git a/apps/workflowengine/composer/composer/autoload_static.php b/apps/workflowengine/composer/composer/autoload_static.php index 01988161ce4..8d54000260c 100644 --- a/apps/workflowengine/composer/composer/autoload_static.php +++ b/apps/workflowengine/composer/composer/autoload_static.php @@ -33,6 +33,7 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Check\\RequestUserAgent' => __DIR__ . '/..' . '/../lib/Check/RequestUserAgent.php', 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => __DIR__ . '/..' . '/../lib/Check/UserGroupMembership.php', 'OCA\\WorkflowEngine\\Controller\\FlowOperations' => __DIR__ . '/..' . '/../lib/Controller/FlowOperations.php', + 'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => __DIR__ . '/..' . '/../lib/Controller/GlobalWorkflowsController.php', 'OCA\\WorkflowEngine\\Controller\\RequestTime' => __DIR__ . '/..' . '/../lib/Controller/RequestTime.php', 'OCA\\WorkflowEngine\\Manager' => __DIR__ . '/..' . '/../lib/Manager.php', 'OCA\\WorkflowEngine\\Settings\\Section' => __DIR__ . '/..' . '/../lib/Settings/Section.php', diff --git a/apps/workflowengine/lib/Controller/GlobalWorkflowsController.php b/apps/workflowengine/lib/Controller/GlobalWorkflowsController.php new file mode 100644 index 00000000000..e4321b9c0b6 --- /dev/null +++ b/apps/workflowengine/lib/Controller/GlobalWorkflowsController.php @@ -0,0 +1,111 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Controller; + +use OCA\WorkflowEngine\Manager; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSBadRequestException; +use OCP\AppFramework\OCSController; +use OCP\IRequest; + +class GlobalWorkflowsController extends OCSController { + + /** @var Manager */ + private $manager; + + public function __construct( + $appName, + IRequest $request, + Manager $manager + ) { + parent::__construct($appName, $request); + + $this->manager = $manager; + } + + /** + * Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global?format=json" + */ + public function index(): DataResponse { + $operationsByClass = $this->manager->getAllOperations(); + + foreach ($operationsByClass as &$operations) { + foreach ($operations as &$operation) { + $operation = $this->manager->formatOperation($operation); + } + } + + return new DataResponse($operationsByClass); + } + + /** + * @throws OCSBadRequestException + * + * Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global/OCA\\Workflow_DocToPdf\\Operation?format=json" + */ + public function show(string $id): DataResponse { + // The ID corresponds to a class name + $operations = $this->manager->getOperations($id); + + foreach ($operations as &$operation) { + $operation = $this->manager->formatOperation($operation); + } + + return new DataResponse($operations); + } + + /** + * @throws OCSBadRequestException + */ + public function create(string $class, string $name, array $checks, string $operation): DataResponse { + try { + $operation = $this->manager->addOperation($class, $name, $checks, $operation); + $operation = $this->manager->formatOperation($operation); + return new DataResponse($operation); + } catch (\UnexpectedValueException $e) { + throw new OCSBadRequestException($e->getMessage(), $e); + } + } + + /** + * @throws OCSBadRequestException + */ + public function update(int $id, string $name, array $checks, string $operation): DataResponse { + try { + $operation = $this->manager->updateOperation($id, $name, $checks, $operation); + $operation = $this->manager->formatOperation($operation); + return new DataResponse($operation); + } catch (\UnexpectedValueException $e) { + throw new OCSBadRequestException($e->getMessage(), $e); + } + } + + /** + */ + public function destroy(int $id): DataResponse { + $deleted = $this->manager->deleteOperation((int) $id); + return new DataResponse($deleted); + } +} diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index 080faa6bfc1..32b04c1021d 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -127,6 +127,25 @@ class Manager implements IManager { } } + public function getAllOperations(): array { + $this->operations = []; + + $query = $this->connection->getQueryBuilder(); + + $query->select('*') + ->from('flow_operations'); + $result = $query->execute(); + + while ($row = $result->fetch()) { + if(!isset($this->operations[$row['class']])) { + $this->operations[$row['class']] = []; + } + $this->operations[$row['class']][] = $row; + } + + return $this->operations; + } + /** * @param string $class * @return array[] @@ -353,4 +372,20 @@ class Manager implements IManager { return $query->getLastInsertId(); } + + public function formatOperation(array $operation): array { + $checkIds = json_decode($operation['checks'], true); + $checks = $this->getChecks($checkIds); + + $operation['checks'] = []; + foreach ($checks as $check) { + // Remove internal values + unset($check['id']); + unset($check['hash']); + + $operation['checks'][] = $check; + } + + return $operation; + } } From 445d6eb8394fa8db68f2f856d8a141eb1cebd34b Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 7 Aug 2019 12:12:56 +0200 Subject: [PATCH 02/68] open the WFE to deal with other subjects but files Signed-off-by: Arthur Schiwon --- apps/workflowengine/lib/Manager.php | 30 ++++++++++++++++-- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/public/WorkflowEngine/IEntityAware.php | 34 +++++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 lib/public/WorkflowEngine/IEntityAware.php diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index 32b04c1021d..23f62da4f8f 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -28,12 +28,14 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Storage\IStorage; use OCP\IDBConnection; use OCP\IL10N; +use OCP\ILogger; use OCP\IServerContainer; use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IEntityAware; use OCP\WorkflowEngine\IManager; use OCP\WorkflowEngine\IOperation; -class Manager implements IManager { +class Manager implements IManager, IEntityAware { /** @var IStorage */ protected $storage; @@ -41,6 +43,9 @@ class Manager implements IManager { /** @var string */ protected $path; + /** @var object */ + protected $entity; + /** @var array[] */ protected $operations = []; @@ -118,7 +123,10 @@ class Manager implements IManager { return true; } - if ($checkInstance instanceof ICheck) { + if ($checkInstance instanceof IEntityAware && $this->entity !== null) { + $checkInstance->setEntity($this->entity); + return $checkInstance->executeCheck($check['operator'], $check['value']); + } elseif ($checkInstance instanceof ICheck) { $checkInstance->setFileInfo($this->storage, $this->path); return $checkInstance->executeCheck($check['operator'], $check['value']); } else { @@ -388,4 +396,22 @@ class Manager implements IManager { return $operation; } + + /** + * @param object $entity + * @since 18.0.0 + */ + public function setEntity($entity) { + if(!is_object($entity)) { + $this->container->getLogger()->logException( + new \InvalidArgumentException('provided entity is not an object'), + [ + 'app' => 'workflowengine', + 'level' => ILogger::ERROR, + ] + ); + return; + } + $this->entity = $entity; + } } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 2c2b8889e26..bee32648915 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -439,6 +439,7 @@ return array( 'OCP\\User\\Backend\\ISetPasswordBackend' => $baseDir . '/lib/public/User/Backend/ISetPasswordBackend.php', 'OCP\\Util' => $baseDir . '/lib/public/Util.php', 'OCP\\WorkflowEngine\\ICheck' => $baseDir . '/lib/public/WorkflowEngine/ICheck.php', + 'OCP\\WorkflowEngine\\IEntityAware' => $baseDir . '/lib/public/WorkflowEngine/IEntityAware.php', 'OCP\\WorkflowEngine\\IManager' => $baseDir . '/lib/public/WorkflowEngine/IManager.php', 'OCP\\WorkflowEngine\\IOperation' => $baseDir . '/lib/public/WorkflowEngine/IOperation.php', 'OC\\Accounts\\Account' => $baseDir . '/lib/private/Accounts/Account.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 6bd401b4d77..eab2897df09 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -473,6 +473,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\User\\Backend\\ISetPasswordBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISetPasswordBackend.php', 'OCP\\Util' => __DIR__ . '/../../..' . '/lib/public/Util.php', 'OCP\\WorkflowEngine\\ICheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ICheck.php', + 'OCP\\WorkflowEngine\\IEntityAware' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityAware.php', 'OCP\\WorkflowEngine\\IManager' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IManager.php', 'OCP\\WorkflowEngine\\IOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IOperation.php', 'OC\\Accounts\\Account' => __DIR__ . '/../../..' . '/lib/private/Accounts/Account.php', diff --git a/lib/public/WorkflowEngine/IEntityAware.php b/lib/public/WorkflowEngine/IEntityAware.php new file mode 100644 index 00000000000..2ef74d2b115 --- /dev/null +++ b/lib/public/WorkflowEngine/IEntityAware.php @@ -0,0 +1,34 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\WorkflowEngine; + + +interface IEntityAware { + /** + * @param object $entity + * @since 18.0.0 + */ + public function setEntity($entity); +} From 9a6f7cc8cb5aef8d0c9dfb29fbed4a02e331e647 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 8 Aug 2019 10:29:17 +0200 Subject: [PATCH 03/68] add scope table for workflows and switch to migrations Signed-off-by: Arthur Schiwon --- apps/workflowengine/appinfo/database.xml | 90 ---------------- .../composer/composer/autoload_classmap.php | 1 + .../composer/composer/autoload_static.php | 1 + .../Version2019Date20190808074233.php | 100 ++++++++++++++++++ 4 files changed, 102 insertions(+), 90 deletions(-) delete mode 100644 apps/workflowengine/appinfo/database.xml create mode 100644 apps/workflowengine/lib/Migration/Version2019Date20190808074233.php diff --git a/apps/workflowengine/appinfo/database.xml b/apps/workflowengine/appinfo/database.xml deleted file mode 100644 index b67a41faed2..00000000000 --- a/apps/workflowengine/appinfo/database.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - *dbname* - true - false - utf8 - - - *dbprefix*flow_checks - - - id - integer - 0 - true - 1 - 4 - - - - class - text - true - 256 - - - operator - text - true - 16 - - - value - clob - false - - - hash - text - true - 32 - - - - flow_unique_hash - true - - hash - - - -
- - - *dbprefix*flow_operations - - - id - integer - 0 - true - 1 - 4 - - - - class - text - true - 256 - - - name - text - true - 256 - - - checks - clob - false - - - operation - clob - false - - -
-
diff --git a/apps/workflowengine/composer/composer/autoload_classmap.php b/apps/workflowengine/composer/composer/autoload_classmap.php index 7d73b3a6241..14fca1fce4d 100644 --- a/apps/workflowengine/composer/composer/autoload_classmap.php +++ b/apps/workflowengine/composer/composer/autoload_classmap.php @@ -21,5 +21,6 @@ return array( 'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => $baseDir . '/../lib/Controller/GlobalWorkflowsController.php', 'OCA\\WorkflowEngine\\Controller\\RequestTime' => $baseDir . '/../lib/Controller/RequestTime.php', 'OCA\\WorkflowEngine\\Manager' => $baseDir . '/../lib/Manager.php', + 'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => $baseDir . '/../lib/Migration/Version2019Date20190808074233.php', 'OCA\\WorkflowEngine\\Settings\\Section' => $baseDir . '/../lib/Settings/Section.php', ); diff --git a/apps/workflowengine/composer/composer/autoload_static.php b/apps/workflowengine/composer/composer/autoload_static.php index 8d54000260c..7af16f53701 100644 --- a/apps/workflowengine/composer/composer/autoload_static.php +++ b/apps/workflowengine/composer/composer/autoload_static.php @@ -36,6 +36,7 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => __DIR__ . '/..' . '/../lib/Controller/GlobalWorkflowsController.php', 'OCA\\WorkflowEngine\\Controller\\RequestTime' => __DIR__ . '/..' . '/../lib/Controller/RequestTime.php', 'OCA\\WorkflowEngine\\Manager' => __DIR__ . '/..' . '/../lib/Manager.php', + 'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => __DIR__ . '/..' . '/../lib/Migration/Version2019Date20190808074233.php', 'OCA\\WorkflowEngine\\Settings\\Section' => __DIR__ . '/..' . '/../lib/Settings/Section.php', ); diff --git a/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php b/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php new file mode 100644 index 00000000000..cedee43a9eb --- /dev/null +++ b/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php @@ -0,0 +1,100 @@ +hasTable('flow_checks')) { + $table = $schema->createTable('flow_checks'); + $table->addColumn('id', Type::INTEGER, [ + 'autoincrement' => true, + 'notnull' => true, + 'length' => 4, + ]); + $table->addColumn('class', Type::STRING, [ + 'notnull' => true, + 'length' => 256, + ]); + $table->addColumn('operator', Type::STRING, [ + 'notnull' => true, + 'length' => 16, + ]); + $table->addColumn('value', Type::TEXT, [ + 'notnull' => false, + ]); + $table->addColumn('hash', Type::STRING, [ + 'notnull' => true, + 'length' => 32, + ]); + $table->setPrimaryKey(['id']); + $table->addUniqueIndex(['hash'], 'flow_unique_hash'); + } + + if (!$schema->hasTable('flow_operations')) { + $table = $schema->createTable('flow_operations'); + $table->addColumn('id', Type::INTEGER, [ + 'autoincrement' => true, + 'notnull' => true, + 'length' => 4, + ]); + $table->addColumn('class', Type::STRING, [ + 'notnull' => true, + 'length' => 256, + ]); + $table->addColumn('name', Type::STRING, [ + 'notnull' => true, + 'length' => 256, + ]); + $table->addColumn('checks', Type::TEXT, [ + 'notnull' => false, + ]); + $table->addColumn('operation', Type::TEXT, [ + 'notnull' => false, + ]); + $table->setPrimaryKey(['id']); + } + + if (!$schema->hasTable('flow_operations_scope')) { + $table = $schema->createTable('flow_operations_scope'); + $table->addColumn('id', Type::BIGINT, [ + 'autoincrement' => true, + 'notnull' => true, + 'length' => 4, + ]); + $table->addColumn('operation_id', Type::INTEGER, [ + 'notnull' => true, + 'length' => 4, + ]); + $table->addColumn('type', Type::INTEGER, [ + 'notnull' => true, + 'length' => 4, + ]); + $table->addColumn('value', Type::STRING, [ + 'notnull' => false, + 'length' => 64, + ]); + $table->setPrimaryKey(['id']); + $table->addUniqueIndex(['operation_id', 'type', 'value'], 'flow_unique_scope'); + } + + return $schema; + } +} From 804d4fe69feb1853846ad7a4b92f01cc637daf71 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 9 Aug 2019 13:24:48 +0200 Subject: [PATCH 04/68] introducing Entity interfaces and a File one as first implementation also adds admin settings that pass entities as initial state Signed-off-by: Arthur Schiwon --- .../composer/composer/autoload_classmap.php | 4 + .../composer/composer/autoload_static.php | 4 + .../lib/AppInfo/Application.php | 8 +- apps/workflowengine/lib/Entity/File.php | 68 ++++++++++ .../lib/Entity/GenericEntityEmitterEvent.php | 59 ++++++++ .../lib/Entity/IEntityEmitterEvent.php | 34 +++++ apps/workflowengine/lib/Manager.php | 56 +++++++- apps/workflowengine/lib/Settings/Admin.php | 126 ++++++++++++++++++ apps/workflowengine/templates/admin.php | 20 +-- apps/workflowengine/tests/ManagerTest.php | 71 +++++++++- lib/composer/composer/autoload_classmap.php | 2 + lib/composer/composer/autoload_static.php | 2 + lib/public/WorkflowEngine/IEntity.php | 84 ++++++++++++ lib/public/WorkflowEngine/IEntityEvent.php | 52 ++++++++ lib/public/WorkflowEngine/IManager.php | 8 ++ 15 files changed, 571 insertions(+), 27 deletions(-) create mode 100644 apps/workflowengine/lib/Entity/File.php create mode 100644 apps/workflowengine/lib/Entity/GenericEntityEmitterEvent.php create mode 100644 apps/workflowengine/lib/Entity/IEntityEmitterEvent.php create mode 100644 apps/workflowengine/lib/Settings/Admin.php create mode 100644 lib/public/WorkflowEngine/IEntity.php create mode 100644 lib/public/WorkflowEngine/IEntityEvent.php diff --git a/apps/workflowengine/composer/composer/autoload_classmap.php b/apps/workflowengine/composer/composer/autoload_classmap.php index 14fca1fce4d..86a77375473 100644 --- a/apps/workflowengine/composer/composer/autoload_classmap.php +++ b/apps/workflowengine/composer/composer/autoload_classmap.php @@ -20,7 +20,11 @@ return array( 'OCA\\WorkflowEngine\\Controller\\FlowOperations' => $baseDir . '/../lib/Controller/FlowOperations.php', 'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => $baseDir . '/../lib/Controller/GlobalWorkflowsController.php', 'OCA\\WorkflowEngine\\Controller\\RequestTime' => $baseDir . '/../lib/Controller/RequestTime.php', + 'OCA\\WorkflowEngine\\Entity\\File' => $baseDir . '/../lib/Entity/File.php', + 'OCA\\WorkflowEngine\\Entity\\GenericEntityEmitterEvent' => $baseDir . '/../lib/Entity/GenericEntityEmitterEvent.php', + 'OCA\\WorkflowEngine\\Entity\\IEntityEmitterEvent' => $baseDir . '/../lib/Entity/IEntityEmitterEvent.php', 'OCA\\WorkflowEngine\\Manager' => $baseDir . '/../lib/Manager.php', 'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => $baseDir . '/../lib/Migration/Version2019Date20190808074233.php', + 'OCA\\WorkflowEngine\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php', 'OCA\\WorkflowEngine\\Settings\\Section' => $baseDir . '/../lib/Settings/Section.php', ); diff --git a/apps/workflowengine/composer/composer/autoload_static.php b/apps/workflowengine/composer/composer/autoload_static.php index 7af16f53701..d13578448cf 100644 --- a/apps/workflowengine/composer/composer/autoload_static.php +++ b/apps/workflowengine/composer/composer/autoload_static.php @@ -35,8 +35,12 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Controller\\FlowOperations' => __DIR__ . '/..' . '/../lib/Controller/FlowOperations.php', 'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => __DIR__ . '/..' . '/../lib/Controller/GlobalWorkflowsController.php', 'OCA\\WorkflowEngine\\Controller\\RequestTime' => __DIR__ . '/..' . '/../lib/Controller/RequestTime.php', + 'OCA\\WorkflowEngine\\Entity\\File' => __DIR__ . '/..' . '/../lib/Entity/File.php', + 'OCA\\WorkflowEngine\\Entity\\GenericEntityEmitterEvent' => __DIR__ . '/..' . '/../lib/Entity/GenericEntityEmitterEvent.php', + 'OCA\\WorkflowEngine\\Entity\\IEntityEmitterEvent' => __DIR__ . '/..' . '/../lib/Entity/IEntityEmitterEvent.php', 'OCA\\WorkflowEngine\\Manager' => __DIR__ . '/..' . '/../lib/Manager.php', 'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => __DIR__ . '/..' . '/../lib/Migration/Version2019Date20190808074233.php', + 'OCA\\WorkflowEngine\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php', 'OCA\\WorkflowEngine\\Settings\\Section' => __DIR__ . '/..' . '/../lib/Settings/Section.php', ); diff --git a/apps/workflowengine/lib/AppInfo/Application.php b/apps/workflowengine/lib/AppInfo/Application.php index 3fee0c3bb8e..358353b6623 100644 --- a/apps/workflowengine/lib/AppInfo/Application.php +++ b/apps/workflowengine/lib/AppInfo/Application.php @@ -27,8 +27,10 @@ use OCA\WorkflowEngine\Controller\FlowOperations; class Application extends \OCP\AppFramework\App { + const APP_ID = 'workflowengine'; + public function __construct() { - parent::__construct('workflowengine'); + parent::__construct(self::APP_ID); $this->getContainer()->registerAlias('FlowOperationsController', FlowOperations::class); $this->getContainer()->registerAlias('RequestTimeController', RequestTime::class); @@ -47,7 +49,7 @@ class Application extends \OCP\AppFramework\App { class_exists(Template::class, true); } - style('workflowengine', [ + style(self::APP_ID, [ 'admin', ]); @@ -59,7 +61,7 @@ class Application extends \OCP\AppFramework\App { 'systemtags/systemtagscollection', ]); - script('workflowengine', [ + script(self::APP_ID, [ 'workflowengine', ]); }, diff --git a/apps/workflowengine/lib/Entity/File.php b/apps/workflowengine/lib/Entity/File.php new file mode 100644 index 00000000000..0ba3502450a --- /dev/null +++ b/apps/workflowengine/lib/Entity/File.php @@ -0,0 +1,68 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Entity; + +use OCP\Files\IRootFolder; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\WorkflowEngine\IEntity; + +class File implements IEntity { + + /** @var IL10N */ + protected $l10n; + /** @var IURLGenerator */ + protected $urlGenerator; + + public function __construct(IL10N $l10n, IURLGenerator $urlGenerator) { + $this->l10n = $l10n; + $this->urlGenerator = $urlGenerator; + } + + public function getId(): string { + return 'WorkflowEngine_Entity_File'; + } + + public function getName(): string { + return $this->l10n->t('File'); + } + + public function getIcon(): string { + return $this->urlGenerator->imagePath('core', 'categories/files.svg'); + } + + public function getEvents(): array { + $emitterClass = IRootFolder::class; + $slot = '\OC\Files'; + return [ + new GenericEntityEmitterEvent($emitterClass, $slot, 'postCreate', $this->l10n->t('File created')), + new GenericEntityEmitterEvent($emitterClass, $slot, 'postWrite', $this->l10n->t('File updated')), + new GenericEntityEmitterEvent($emitterClass, $slot, 'postRename', $this->l10n->t('File renamed')), + new GenericEntityEmitterEvent($emitterClass, $slot, 'postDelete', $this->l10n->t('File deleted')), + new GenericEntityEmitterEvent($emitterClass, $slot, 'postTouch', $this->l10n->t('File accessed')), + new GenericEntityEmitterEvent($emitterClass, $slot, 'postCopy', $this->l10n->t('File copied')), + ]; + } +} diff --git a/apps/workflowengine/lib/Entity/GenericEntityEmitterEvent.php b/apps/workflowengine/lib/Entity/GenericEntityEmitterEvent.php new file mode 100644 index 00000000000..60b238432c9 --- /dev/null +++ b/apps/workflowengine/lib/Entity/GenericEntityEmitterEvent.php @@ -0,0 +1,59 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Entity; + +class GenericEntityEmitterEvent implements IEntityEmitterEvent { + /** @var string */ + private $emitterClassName; + /** @var string */ + private $eventName; + /** @var string */ + private $displayName; + /** @var string */ + private $slot; + + public function __construct(string $emitterClassName, string $slot, string $eventName, string $displayName) { + $this->emitterClassName = $emitterClassName; + $this->eventName = $eventName; + $this->displayName = $displayName; + $this->slot = $slot; + } + + public function getEmitterClassName(): string { + return $this->emitterClassName; + } + + public function getSlot(): string { + return $this->slot; + } + + public function getDisplayName(): string { + return $this->displayName; + } + + public function getEventName(): string { + return $this->eventName; + } +} diff --git a/apps/workflowengine/lib/Entity/IEntityEmitterEvent.php b/apps/workflowengine/lib/Entity/IEntityEmitterEvent.php new file mode 100644 index 00000000000..7e2c802fe76 --- /dev/null +++ b/apps/workflowengine/lib/Entity/IEntityEmitterEvent.php @@ -0,0 +1,34 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Entity; + + +use OCP\WorkflowEngine\IEntityEvent; + +interface IEntityEmitterEvent extends IEntityEvent { + public function getEmitterClassName(): string; + + public function getSlot(): string; +} diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index 23f62da4f8f..3a382f20dcd 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -23,6 +23,7 @@ namespace OCA\WorkflowEngine; use OC\Files\Storage\Wrapper\Jail; +use OCA\WorkflowEngine\Entity\File; use OCP\AppFramework\QueryException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Storage\IStorage; @@ -31,9 +32,12 @@ use OCP\IL10N; use OCP\ILogger; use OCP\IServerContainer; use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IEntityAware; use OCP\WorkflowEngine\IManager; use OCP\WorkflowEngine\IOperation; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; class Manager implements IManager, IEntityAware { @@ -61,15 +65,32 @@ class Manager implements IManager, IEntityAware { /** @var IL10N */ protected $l; + /** @var EventDispatcherInterface */ + protected $eventDispatcher; + + /** @var IEntity[] */ + protected $registeredEntities = []; + + /** @var ILogger */ + protected $logger; + /** * @param IDBConnection $connection * @param IServerContainer $container * @param IL10N $l */ - public function __construct(IDBConnection $connection, IServerContainer $container, IL10N $l) { + public function __construct( + IDBConnection $connection, + IServerContainer $container, + IL10N $l, + EventDispatcherInterface $eventDispatcher, + ILogger $logger + ) { $this->connection = $connection; $this->container = $container; $this->l = $l; + $this->eventDispatcher = $eventDispatcher; + $this->logger = $logger; } /** @@ -414,4 +435,37 @@ class Manager implements IManager, IEntityAware { } $this->entity = $entity; } + + /** + * @return IEntity[] + */ + public function getEntitiesList() { + $this->eventDispatcher->dispatch('OCP\WorkflowEngine::registerEntities', new GenericEvent($this)); + + return array_merge($this->getBuildInEntities(), $this->registeredEntities); + } + + /** + * Listen to 'OCP/WorkflowEngine::registerEntities' at the EventDispatcher + * for registering your entities + * + * @since 18.0.0 + */ + public function registerEntity(IEntity $entity): void { + $this->registeredEntities[$entity->getId()] = $entity; + } + + /** + * @return IEntity[] + */ + protected function getBuildInEntities(): array { + try { + return [ + $this->container->query(File::class), + ]; + } catch (QueryException $e) { + $this->logger->logException($e); + return []; + } + } } diff --git a/apps/workflowengine/lib/Settings/Admin.php b/apps/workflowengine/lib/Settings/Admin.php new file mode 100644 index 00000000000..8ffd8533d3b --- /dev/null +++ b/apps/workflowengine/lib/Settings/Admin.php @@ -0,0 +1,126 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Settings; + +use OCA\WorkflowEngine\AppInfo\Application; +use OCA\WorkflowEngine\Manager; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\IInitialStateService; +use OCP\IL10N; +use OCP\Settings\ISettings; +use OCP\WorkflowEngine\IEntity; +use OCP\WorkflowEngine\IEntityEvent; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class Admin implements ISettings { + + /** @var IL10N */ + private $l10n; + + /** @var string */ + private $appName; + + /** @var EventDispatcherInterface */ + private $eventDispatcher; + + /** @var Manager */ + private $manager; + + /** @var IInitialStateService */ + private $initialStateService; + + /** + * @param string $appName + * @param IL10N $l + * @param EventDispatcherInterface $eventDispatcher + */ + public function __construct( + $appName, + IL10N $l, + EventDispatcherInterface $eventDispatcher, + Manager $manager, + IInitialStateService $initialStateService + ) { + $this->appName = $appName; + $this->l10n = $l; + $this->eventDispatcher = $eventDispatcher; + $this->manager = $manager; + $this->initialStateService = $initialStateService; + } + + /** + * @return TemplateResponse + */ + public function getForm() { + $this->eventDispatcher->dispatch('OCP\WorkflowEngine::loadAdditionalSettingScripts'); + + $entities = $this->manager->getEntitiesList(); + + $this->initialStateService->provideInitialState( + Application::APP_ID, + 'entities', + $this->entitiesToArray($entities) + ); + + return new TemplateResponse(Application::APP_ID, 'admin', [], 'blank'); + } + + /** + * @return string the section ID, e.g. 'sharing' + */ + public function getSection() { + return 'workflow'; + } + + /** + * @return int whether the form should be rather on the top or bottom of + * the admin section. The forms are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * + * E.g.: 70 + */ + public function getPriority() { + return 0; + } + + private function entitiesToArray(array $entities) { + return array_map(function (IEntity $entity) { + $events = array_map(function(IEntityEvent $entityEvent) { + return [ + 'eventName' => $entityEvent->getEventName(), + 'displayName' => $entityEvent->getDisplayName() + ]; + }, $entity->getEvents()); + + return [ + 'id' => $entity->getId(), + 'icon' => $entity->getIcon(), + 'name' => $entity->getName(), + 'events' => $events, + ]; + }, $entities); + } + +} diff --git a/apps/workflowengine/templates/admin.php b/apps/workflowengine/templates/admin.php index d3faace71f6..04f43bb2573 100644 --- a/apps/workflowengine/templates/admin.php +++ b/apps/workflowengine/templates/admin.php @@ -22,22 +22,4 @@ /** @var array $_ */ /** @var \OCP\IL10N $l */ ?> -
-

- - - - - - -

- - - -

- - -
t('Loading…')); ?>
-
+
diff --git a/apps/workflowengine/tests/ManagerTest.php b/apps/workflowengine/tests/ManagerTest.php index 9136bf0e7cd..ecfc92713a0 100644 --- a/apps/workflowengine/tests/ManagerTest.php +++ b/apps/workflowengine/tests/ManagerTest.php @@ -22,10 +22,15 @@ namespace OCA\WorkflowEngine\Tests; +use OCA\WorkflowEngine\Entity\File; use OCA\WorkflowEngine\Manager; use OCP\IDBConnection; use OCP\IL10N; +use OCP\ILogger; use OCP\IServerContainer; +use OCP\WorkflowEngine\IEntity; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Test\TestCase; /** @@ -38,24 +43,36 @@ class ManagerTest extends TestCase { /** @var Manager */ protected $manager; - /** @var IDBConnection */ + /** @var MockObject|IDBConnection */ protected $db; + /** @var \PHPUnit\Framework\MockObject\MockObject|ILogger */ + protected $logger; + /** @var \PHPUnit\Framework\MockObject\MockObject|EventDispatcherInterface */ + protected $eventDispatcher; + /** @var MockObject|IServerContainer */ + protected $container; protected function setUp() { parent::setUp(); $this->db = \OC::$server->getDatabaseConnection(); - $container = $this->createMock(IServerContainer::class); + $this->container = $this->createMock(IServerContainer::class); + /** @var IL10N|MockObject $l */ $l = $this->createMock(IL10N::class); $l->method('t') ->will($this->returnCallback(function($text, $parameters = []) { return vsprintf($text, $parameters); })); + $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); + $this->logger = $this->createMock(ILogger::class); + $this->manager = new Manager( \OC::$server->getDatabaseConnection(), - $container, - $l + $this->container, + $l, + $this->eventDispatcher, + $this->logger ); $this->clearChecks(); } @@ -91,4 +108,50 @@ class ManagerTest extends TestCase { $this->assertArrayNotHasKey($check1, $data); $this->assertArrayHasKey($check2, $data); } + + public function testGetEntitiesListBuildInOnly() { + $fileEntityMock = $this->createMock(File::class); + + $this->container->expects($this->once()) + ->method('query') + ->with(File::class) + ->willReturn($fileEntityMock); + + $entities = $this->manager->getEntitiesList(); + + $this->assertCount(1, $entities); + $this->assertInstanceOf(IEntity::class, $entities[0]); + } + + public function testGetEntitiesList() { + $fileEntityMock = $this->createMock(File::class); + + $this->container->expects($this->once()) + ->method('query') + ->with(File::class) + ->willReturn($fileEntityMock); + + /** @var MockObject|IEntity $extraEntity */ + $extraEntity = $this->createMock(IEntity::class); + + $this->eventDispatcher->expects($this->once()) + ->method('dispatch') + ->with('OCP\WorkflowEngine::registerEntities', $this->anything()) + ->willReturnCallback(function() use ($extraEntity) { + $this->manager->registerEntity($extraEntity); + }); + + $entities = $this->manager->getEntitiesList(); + + $this->assertCount(2, $entities); + + $entityTypeCounts = array_reduce($entities, function (array $carry, IEntity $entity) { + if($entity instanceof File) $carry[0]++; + else if($entity instanceof IEntity) $carry[1]++; + return $carry; + }, [0, 0]); + + $this->assertSame(1, $entityTypeCounts[0]); + $this->assertSame(1, $entityTypeCounts[1]); + } } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index bee32648915..2c76674ee52 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -439,7 +439,9 @@ return array( 'OCP\\User\\Backend\\ISetPasswordBackend' => $baseDir . '/lib/public/User/Backend/ISetPasswordBackend.php', 'OCP\\Util' => $baseDir . '/lib/public/Util.php', 'OCP\\WorkflowEngine\\ICheck' => $baseDir . '/lib/public/WorkflowEngine/ICheck.php', + 'OCP\\WorkflowEngine\\IEntity' => $baseDir . '/lib/public/WorkflowEngine/IEntity.php', 'OCP\\WorkflowEngine\\IEntityAware' => $baseDir . '/lib/public/WorkflowEngine/IEntityAware.php', + 'OCP\\WorkflowEngine\\IEntityEvent' => $baseDir . '/lib/public/WorkflowEngine/IEntityEvent.php', 'OCP\\WorkflowEngine\\IManager' => $baseDir . '/lib/public/WorkflowEngine/IManager.php', 'OCP\\WorkflowEngine\\IOperation' => $baseDir . '/lib/public/WorkflowEngine/IOperation.php', 'OC\\Accounts\\Account' => $baseDir . '/lib/private/Accounts/Account.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index eab2897df09..651364d06c1 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -473,7 +473,9 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\User\\Backend\\ISetPasswordBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISetPasswordBackend.php', 'OCP\\Util' => __DIR__ . '/../../..' . '/lib/public/Util.php', 'OCP\\WorkflowEngine\\ICheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ICheck.php', + 'OCP\\WorkflowEngine\\IEntity' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntity.php', 'OCP\\WorkflowEngine\\IEntityAware' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityAware.php', + 'OCP\\WorkflowEngine\\IEntityEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityEvent.php', 'OCP\\WorkflowEngine\\IManager' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IManager.php', 'OCP\\WorkflowEngine\\IOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IOperation.php', 'OC\\Accounts\\Account' => __DIR__ . '/../../..' . '/lib/private/Accounts/Account.php', diff --git a/lib/public/WorkflowEngine/IEntity.php b/lib/public/WorkflowEngine/IEntity.php new file mode 100644 index 00000000000..2e77b741367 --- /dev/null +++ b/lib/public/WorkflowEngine/IEntity.php @@ -0,0 +1,84 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\WorkflowEngine; + +/** + * Interface IEntity + * + * This interface represents an entity that supports events the workflow engine + * can listen to. For example a file with the create, update, etc. events. + * + * Ensure to listen to 'OCP/WorkflowEngine::loadEntities' for registering your + * entities. + * + * @package OCP\WorkflowEngine + * @since 18.0.0 + */ +interface IEntity { + + /** + * returns a unique ID of the entity. + * + * It can be, but does not need to be the class name of the entitiy. Beware + * that it will be referenced in the database when rules are established, + * so it should not change over the course of the app life. + * + * Example 1: "OCA/MyApp/Entity/Cat" + * Example 2: "myapp_Cats" + * + * @since 18.0.0 + */ + public function getId(): string; + + /** + * returns a translated name to be presented in the web interface. + * + * Example: "File" (en), "Dosiero" (eo) + * + * @since 18.0.0 + */ + public function getName(): string; + + /** + * returns the URL to the icon of the entity for display in the web interface. + * + * Usually, the implementation would utilize the `imagePath()` method of the + * `\OCP\IURLGenerator` instance and simply return its result. + * + * Example implementation: return $this->urlGenerator->imagePath('myApp', 'cat.svg'); + * + * @since 18.0.0 + */ + public function getIcon(): string; + + /** + * returns a list of supported events + * + * @return IEntityEvent[] + * @since 18.0.0 + */ + public function getEvents(): array; + +} diff --git a/lib/public/WorkflowEngine/IEntityEvent.php b/lib/public/WorkflowEngine/IEntityEvent.php new file mode 100644 index 00000000000..89186e808ed --- /dev/null +++ b/lib/public/WorkflowEngine/IEntityEvent.php @@ -0,0 +1,52 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\WorkflowEngine; + +/** + * Interface IEntityEvent + * + * represents an entitiy event that is dispatched via EventDispatcher + * + * @package OCP\WorkflowEngine + */ +interface IEntityEvent { + /** + * returns a translated name to be presented in the web interface. + * + * Example: "created" (en), "kreita" (eo) + * + * @since 18.0.0 + */ + public function getDisplayName(): string; + + /** + * returns the event name that is emitted by the EventDispatcher, e.g.: + * + * Example: "OCA\MyApp\Factory\Cats::postCreated" + * + * @since 18.0.0 + */ + public function getEventName(): string; +} diff --git a/lib/public/WorkflowEngine/IManager.php b/lib/public/WorkflowEngine/IManager.php index cd323a816f3..33a1dd1bb6c 100644 --- a/lib/public/WorkflowEngine/IManager.php +++ b/lib/public/WorkflowEngine/IManager.php @@ -47,4 +47,12 @@ interface IManager { * @since 9.1 */ public function getMatchingOperations($class, $returnFirstMatchingOperationOnly = true); + + /** + * Listen to 'OCP/WorkflowEngine::registerEntities' at the EventDispatcher + * for registering your entities + * + * @since 18.0.0 + */ + public function registerEntity(IEntity $entity): void; } From bd5c455da4d79458906549082b49b0b83deebee8 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 16 Aug 2019 17:17:38 +0200 Subject: [PATCH 05/68] the workflow manager becomes scope aware, Part 1 Signed-off-by: Arthur Schiwon --- apps/workflowengine/appinfo/info.xml | 18 ++++- .../composer/composer/autoload_classmap.php | 2 + .../composer/composer/autoload_static.php | 2 + apps/workflowengine/lib/Command/Index.php | 77 +++++++++++++++++++ apps/workflowengine/lib/Manager.php | 33 +++++++- .../PopulateNewlyIntroducedDatabaseFields.php | 76 ++++++++++++++++++ lib/public/WorkflowEngine/IManager.php | 4 + 7 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 apps/workflowengine/lib/Command/Index.php create mode 100644 apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php diff --git a/apps/workflowengine/appinfo/info.xml b/apps/workflowengine/appinfo/info.xml index a2f216b2957..ddb20538280 100644 --- a/apps/workflowengine/appinfo/info.xml +++ b/apps/workflowengine/appinfo/info.xml @@ -2,11 +2,13 @@ workflowengine - Files workflow engine - Files workflow engine - Files workflow engine + Nextcloud workflow engine + Nextcloud workflow engine + Nextcloud workflow engine 1.8.0 agpl + Arthur Schiwon + Julius Härtl Morris Jobke WorkflowEngine @@ -23,6 +25,16 @@ + + + OCA\WorkflowEngine\Migration\PopulateNewlyIntroducedDatabaseFields + + + + + OCA\WorkflowEngine\Command\Index + + OCA\WorkflowEngine\Settings\Section diff --git a/apps/workflowengine/composer/composer/autoload_classmap.php b/apps/workflowengine/composer/composer/autoload_classmap.php index 86a77375473..269a41a96b0 100644 --- a/apps/workflowengine/composer/composer/autoload_classmap.php +++ b/apps/workflowengine/composer/composer/autoload_classmap.php @@ -17,6 +17,7 @@ return array( 'OCA\\WorkflowEngine\\Check\\RequestURL' => $baseDir . '/../lib/Check/RequestURL.php', 'OCA\\WorkflowEngine\\Check\\RequestUserAgent' => $baseDir . '/../lib/Check/RequestUserAgent.php', 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => $baseDir . '/../lib/Check/UserGroupMembership.php', + 'OCA\\WorkflowEngine\\Command\\Index' => $baseDir . '/../lib/Command/Index.php', 'OCA\\WorkflowEngine\\Controller\\FlowOperations' => $baseDir . '/../lib/Controller/FlowOperations.php', 'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => $baseDir . '/../lib/Controller/GlobalWorkflowsController.php', 'OCA\\WorkflowEngine\\Controller\\RequestTime' => $baseDir . '/../lib/Controller/RequestTime.php', @@ -24,6 +25,7 @@ return array( 'OCA\\WorkflowEngine\\Entity\\GenericEntityEmitterEvent' => $baseDir . '/../lib/Entity/GenericEntityEmitterEvent.php', 'OCA\\WorkflowEngine\\Entity\\IEntityEmitterEvent' => $baseDir . '/../lib/Entity/IEntityEmitterEvent.php', 'OCA\\WorkflowEngine\\Manager' => $baseDir . '/../lib/Manager.php', + 'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => $baseDir . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php', 'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => $baseDir . '/../lib/Migration/Version2019Date20190808074233.php', 'OCA\\WorkflowEngine\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php', 'OCA\\WorkflowEngine\\Settings\\Section' => $baseDir . '/../lib/Settings/Section.php', diff --git a/apps/workflowengine/composer/composer/autoload_static.php b/apps/workflowengine/composer/composer/autoload_static.php index d13578448cf..eed3f208f86 100644 --- a/apps/workflowengine/composer/composer/autoload_static.php +++ b/apps/workflowengine/composer/composer/autoload_static.php @@ -32,6 +32,7 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Check\\RequestURL' => __DIR__ . '/..' . '/../lib/Check/RequestURL.php', 'OCA\\WorkflowEngine\\Check\\RequestUserAgent' => __DIR__ . '/..' . '/../lib/Check/RequestUserAgent.php', 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => __DIR__ . '/..' . '/../lib/Check/UserGroupMembership.php', + 'OCA\\WorkflowEngine\\Command\\Index' => __DIR__ . '/..' . '/../lib/Command/Index.php', 'OCA\\WorkflowEngine\\Controller\\FlowOperations' => __DIR__ . '/..' . '/../lib/Controller/FlowOperations.php', 'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => __DIR__ . '/..' . '/../lib/Controller/GlobalWorkflowsController.php', 'OCA\\WorkflowEngine\\Controller\\RequestTime' => __DIR__ . '/..' . '/../lib/Controller/RequestTime.php', @@ -39,6 +40,7 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Entity\\GenericEntityEmitterEvent' => __DIR__ . '/..' . '/../lib/Entity/GenericEntityEmitterEvent.php', 'OCA\\WorkflowEngine\\Entity\\IEntityEmitterEvent' => __DIR__ . '/..' . '/../lib/Entity/IEntityEmitterEvent.php', 'OCA\\WorkflowEngine\\Manager' => __DIR__ . '/..' . '/../lib/Manager.php', + 'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => __DIR__ . '/..' . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php', 'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => __DIR__ . '/..' . '/../lib/Migration/Version2019Date20190808074233.php', 'OCA\\WorkflowEngine\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php', 'OCA\\WorkflowEngine\\Settings\\Section' => __DIR__ . '/..' . '/../lib/Settings/Section.php', diff --git a/apps/workflowengine/lib/Command/Index.php b/apps/workflowengine/lib/Command/Index.php new file mode 100644 index 00000000000..8098bc5d5ea --- /dev/null +++ b/apps/workflowengine/lib/Command/Index.php @@ -0,0 +1,77 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Command; + +use OCA\WorkflowEngine\Manager; +use OCP\WorkflowEngine\IManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Index extends Command { + + /** @var Manager */ + private $manager; + + public function __construct(Manager $manager) { + $this->manager = $manager; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('workflows:list') + ->setDescription('Lists configured workflows') + ->addArgument( + 'scope', + InputArgument::OPTIONAL, + 'Lists workflows for "admin", "user"', + 'admin' + ) + ->addArgument( + 'scopeId', + InputArgument::OPTIONAL, + 'User IDs when the scope is "user"', + null + ); + } + + protected function mappedScope(string $scope): int { + static $scopes = [ + 'admin' => IManager::SCOPE_ADMIN, + 'user' => IManager::SCOPE_USER, + ]; + return $scopes[$scope] ?? -1; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $ops = $this->manager->getAllOperations( + $this->mappedScope($input->getArgument('scope')), + $input->getArgument('scopeId') + ); + $output->writeln(\json_encode($ops)); + } +} diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index 3a382f20dcd..3bfedfbfbf4 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -31,6 +31,7 @@ use OCP\IDBConnection; use OCP\IL10N; use OCP\ILogger; use OCP\IServerContainer; +use OCP\IUserSession; use OCP\WorkflowEngine\ICheck; use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IEntityAware; @@ -73,6 +74,8 @@ class Manager implements IManager, IEntityAware { /** @var ILogger */ protected $logger; + /** @var IUserSession */ + protected $session; /** * @param IDBConnection $connection @@ -84,13 +87,15 @@ class Manager implements IManager, IEntityAware { IServerContainer $container, IL10N $l, EventDispatcherInterface $eventDispatcher, - ILogger $logger + ILogger $logger, + IUserSession $session ) { $this->connection = $connection; $this->container = $container; $this->l = $l; $this->eventDispatcher = $eventDispatcher; $this->logger = $logger; + $this->session = $session; } /** @@ -155,14 +160,33 @@ class Manager implements IManager, IEntityAware { throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class'])); } } + public function getAllOperations(int $scope = IManager::SCOPE_ADMIN, string $scopeId = null): array { + if(!in_array($scope, [IManager::SCOPE_ADMIN, IManager::SCOPE_USER])) { + throw new \InvalidArgumentException('Provided value for scope is not supported'); + } + if($scope === IManager::SCOPE_USER && $scopeId === null) { + $user = $this->session->getUser(); + if($user === null) { + throw new \InvalidArgumentException('No user ID was provided'); + } + $scopeId = $user->getUID(); + } - public function getAllOperations(): array { $this->operations = []; $query = $this->connection->getQueryBuilder(); - $query->select('*') - ->from('flow_operations'); + $query->select('o.*') + ->from('flow_operations', 'o') + ->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id')) + ->where($query->expr()->eq('s.type', $query->createParameter('scope'))); + + if($scope === IManager::SCOPE_USER) { + $query->andWhere($query->expr()->eq('s.value', $query->createParameter('scopeId'))); + } + + $query->setParameters(['scope' => $scope, 'scopeId' => $scopeId]); + $result = $query->execute(); while ($row = $result->fetch()) { @@ -175,6 +199,7 @@ class Manager implements IManager, IEntityAware { return $this->operations; } + /** * @param string $class * @return array[] diff --git a/apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php b/apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php new file mode 100644 index 00000000000..43595d1c7cf --- /dev/null +++ b/apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php @@ -0,0 +1,76 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Migration; + +use Doctrine\DBAL\Driver\Statement; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; +use OCP\WorkflowEngine\IManager; + +class PopulateNewlyIntroducedDatabaseFields implements IRepairStep { + + /** @var IDBConnection */ + private $dbc; + + public function __construct(IDBConnection $dbc) { + $this->dbc = $dbc; + } + + public function getName() { + return 'Populating added database structures for workflows'; + } + + public function run(IOutput $output) { + $result = $this->getIdsWithoutScope(); + + $this->populateScopeTable($result); + + $result->closeCursor(); + } + + protected function populateScopeTable(Statement $ids): void { + $qb = $this->dbc->getQueryBuilder(); + + $insertQuery = $qb->insert('flow_operations_scope'); + while($id = $ids->fetchColumn(0)) { + $insertQuery->values(['operation_id' => $qb->createNamedParameter($id), 'type' => IManager::SCOPE_ADMIN]); + } + $insertQuery->execute(); + } + + protected function getIdsWithoutScope(): Statement { + $qb = $this->dbc->getQueryBuilder(); + $selectQuery = $qb->select('o.id') + ->from('flow_operations', 'o') + ->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id')) + ->where($qb->expr()->isNull('s.operation_id')); + // The left join operation is not necessary, usually, but it's a safe-guard + // in case the repair step is executed multiple times for whatever reason. + + return $selectQuery->execute(); + } + +} diff --git a/lib/public/WorkflowEngine/IManager.php b/lib/public/WorkflowEngine/IManager.php index 33a1dd1bb6c..c05459e1fb4 100644 --- a/lib/public/WorkflowEngine/IManager.php +++ b/lib/public/WorkflowEngine/IManager.php @@ -33,6 +33,10 @@ use OCP\Files\Storage\IStorage; * @since 9.1 */ interface IManager { + + const SCOPE_ADMIN = 0; + const SCOPE_USER = 1; + /** * @param IStorage $storage * @param string $path From 4aba1f1cff194fd8d0af20f9d80c878152fc5e00 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 19 Aug 2019 17:13:47 +0200 Subject: [PATCH 06/68] scope aware workflow controller and manager Signed-off-by: Arthur Schiwon --- .../composer/composer/autoload_classmap.php | 3 + .../composer/composer/autoload_static.php | 3 + apps/workflowengine/lib/Command/Index.php | 7 +- .../lib/Controller/AWorkflowController.php | 148 +++++++++++ .../Controller/GlobalWorkflowsController.php | 88 +------ .../Controller/UserWorkflowsController.php | 117 ++++++++ .../lib/Helper/ScopeContext.php | 78 ++++++ apps/workflowengine/lib/Manager.php | 227 +++++++++++----- apps/workflowengine/tests/ManagerTest.php | 249 +++++++++++++++++- 9 files changed, 761 insertions(+), 159 deletions(-) create mode 100644 apps/workflowengine/lib/Controller/AWorkflowController.php create mode 100644 apps/workflowengine/lib/Controller/UserWorkflowsController.php create mode 100644 apps/workflowengine/lib/Helper/ScopeContext.php diff --git a/apps/workflowengine/composer/composer/autoload_classmap.php b/apps/workflowengine/composer/composer/autoload_classmap.php index 269a41a96b0..81047b10614 100644 --- a/apps/workflowengine/composer/composer/autoload_classmap.php +++ b/apps/workflowengine/composer/composer/autoload_classmap.php @@ -18,12 +18,15 @@ return array( 'OCA\\WorkflowEngine\\Check\\RequestUserAgent' => $baseDir . '/../lib/Check/RequestUserAgent.php', 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => $baseDir . '/../lib/Check/UserGroupMembership.php', 'OCA\\WorkflowEngine\\Command\\Index' => $baseDir . '/../lib/Command/Index.php', + 'OCA\\WorkflowEngine\\Controller\\AWorkflowController' => $baseDir . '/../lib/Controller/AWorkflowController.php', 'OCA\\WorkflowEngine\\Controller\\FlowOperations' => $baseDir . '/../lib/Controller/FlowOperations.php', 'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => $baseDir . '/../lib/Controller/GlobalWorkflowsController.php', 'OCA\\WorkflowEngine\\Controller\\RequestTime' => $baseDir . '/../lib/Controller/RequestTime.php', + 'OCA\\WorkflowEngine\\Controller\\UserWorkflowsController' => $baseDir . '/../lib/Controller/UserWorkflowsController.php', 'OCA\\WorkflowEngine\\Entity\\File' => $baseDir . '/../lib/Entity/File.php', 'OCA\\WorkflowEngine\\Entity\\GenericEntityEmitterEvent' => $baseDir . '/../lib/Entity/GenericEntityEmitterEvent.php', 'OCA\\WorkflowEngine\\Entity\\IEntityEmitterEvent' => $baseDir . '/../lib/Entity/IEntityEmitterEvent.php', + 'OCA\\WorkflowEngine\\Helper\\ScopeContext' => $baseDir . '/../lib/Helper/ScopeContext.php', 'OCA\\WorkflowEngine\\Manager' => $baseDir . '/../lib/Manager.php', 'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => $baseDir . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php', 'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => $baseDir . '/../lib/Migration/Version2019Date20190808074233.php', diff --git a/apps/workflowengine/composer/composer/autoload_static.php b/apps/workflowengine/composer/composer/autoload_static.php index eed3f208f86..d016377ea54 100644 --- a/apps/workflowengine/composer/composer/autoload_static.php +++ b/apps/workflowengine/composer/composer/autoload_static.php @@ -33,12 +33,15 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Check\\RequestUserAgent' => __DIR__ . '/..' . '/../lib/Check/RequestUserAgent.php', 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => __DIR__ . '/..' . '/../lib/Check/UserGroupMembership.php', 'OCA\\WorkflowEngine\\Command\\Index' => __DIR__ . '/..' . '/../lib/Command/Index.php', + 'OCA\\WorkflowEngine\\Controller\\AWorkflowController' => __DIR__ . '/..' . '/../lib/Controller/AWorkflowController.php', 'OCA\\WorkflowEngine\\Controller\\FlowOperations' => __DIR__ . '/..' . '/../lib/Controller/FlowOperations.php', 'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => __DIR__ . '/..' . '/../lib/Controller/GlobalWorkflowsController.php', 'OCA\\WorkflowEngine\\Controller\\RequestTime' => __DIR__ . '/..' . '/../lib/Controller/RequestTime.php', + 'OCA\\WorkflowEngine\\Controller\\UserWorkflowsController' => __DIR__ . '/..' . '/../lib/Controller/UserWorkflowsController.php', 'OCA\\WorkflowEngine\\Entity\\File' => __DIR__ . '/..' . '/../lib/Entity/File.php', 'OCA\\WorkflowEngine\\Entity\\GenericEntityEmitterEvent' => __DIR__ . '/..' . '/../lib/Entity/GenericEntityEmitterEvent.php', 'OCA\\WorkflowEngine\\Entity\\IEntityEmitterEvent' => __DIR__ . '/..' . '/../lib/Entity/IEntityEmitterEvent.php', + 'OCA\\WorkflowEngine\\Helper\\ScopeContext' => __DIR__ . '/..' . '/../lib/Helper/ScopeContext.php', 'OCA\\WorkflowEngine\\Manager' => __DIR__ . '/..' . '/../lib/Manager.php', 'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => __DIR__ . '/..' . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php', 'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => __DIR__ . '/..' . '/../lib/Migration/Version2019Date20190808074233.php', diff --git a/apps/workflowengine/lib/Command/Index.php b/apps/workflowengine/lib/Command/Index.php index 8098bc5d5ea..7f3af3c0811 100644 --- a/apps/workflowengine/lib/Command/Index.php +++ b/apps/workflowengine/lib/Command/Index.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace OCA\WorkflowEngine\Command; +use OCA\WorkflowEngine\Helper\ScopeContext; use OCA\WorkflowEngine\Manager; use OCP\WorkflowEngine\IManager; use Symfony\Component\Console\Command\Command; @@ -69,8 +70,10 @@ class Index extends Command { protected function execute(InputInterface $input, OutputInterface $output) { $ops = $this->manager->getAllOperations( - $this->mappedScope($input->getArgument('scope')), - $input->getArgument('scopeId') + new ScopeContext( + $this->mappedScope($input->getArgument('scope')), + $input->getArgument('scopeId') + ) ); $output->writeln(\json_encode($ops)); } diff --git a/apps/workflowengine/lib/Controller/AWorkflowController.php b/apps/workflowengine/lib/Controller/AWorkflowController.php new file mode 100644 index 00000000000..2e54e417a34 --- /dev/null +++ b/apps/workflowengine/lib/Controller/AWorkflowController.php @@ -0,0 +1,148 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Controller; + +use Doctrine\DBAL\DBALException; +use OCA\WorkflowEngine\Helper\ScopeContext; +use OCA\WorkflowEngine\Manager; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSBadRequestException; +use OCP\AppFramework\OCS\OCSException; +use OCP\AppFramework\OCS\OCSForbiddenException; +use OCP\AppFramework\OCSController; +use OCP\IRequest; + +abstract class AWorkflowController extends OCSController { + + /** @var Manager */ + protected $manager; + + public function __construct( + $appName, + IRequest $request, + Manager $manager + ) { + parent::__construct($appName, $request); + + $this->manager = $manager; + } + + /** + * @throws OCSForbiddenException + */ + abstract protected function getScopeContext(): ScopeContext; + + /** + * Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global?format=json" + * + * @throws OCSForbiddenException + */ + public function index(): DataResponse { + $operationsByClass = $this->manager->getAllOperations($this->getScopeContext()); + + foreach ($operationsByClass as &$operations) { + foreach ($operations as &$operation) { + $operation = $this->manager->formatOperation($operation); + } + } + + return new DataResponse($operationsByClass); + } + + /** + * Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global/OCA\\Workflow_DocToPdf\\Operation?format=json" + * + * @throws OCSForbiddenException + */ + public function show(string $id): DataResponse { + $context = $this->getScopeContext(); + + // The ID corresponds to a class name + $operations = $this->manager->getOperations($id, $context); + + foreach ($operations as &$operation) { + $operation = $this->manager->formatOperation($operation); + } + + return new DataResponse($operations); + } + + /** + * @throws OCSBadRequestException + * @throws OCSForbiddenException + * @throws OCSException + */ + public function create(string $class, string $name, array $checks, string $operation): DataResponse { + $context = $this->getScopeContext(); + try { + $operation = $this->manager->addOperation($class, $name, $checks, $operation, $context); + $operation = $this->manager->formatOperation($operation); + return new DataResponse($operation); + } catch (\UnexpectedValueException $e) { + throw new OCSBadRequestException($e->getMessage(), $e); + } catch (\DomainException $e) { + throw new OCSForbiddenException($e->getMessage(), $e); + } catch(DBALException $e) { + throw new OCSException('An internal error occurred', $e); + } + } + + /** + * @throws OCSBadRequestException + * @throws OCSForbiddenException + * @throws OCSException + */ + public function update(int $id, string $name, array $checks, string $operation): DataResponse { + try { + $operation = $this->manager->updateOperation($id, $name, $checks, $operation, $this->getScopeContext()); + $operation = $this->manager->formatOperation($operation); + return new DataResponse($operation); + } catch (\UnexpectedValueException $e) { + throw new OCSBadRequestException($e->getMessage(), $e); + } catch (\DomainException $e) { + throw new OCSForbiddenException($e->getMessage(), $e); + } catch(DBALException $e) { + throw new OCSException('An internal error occurred', $e); + } + } + + /** + * @throws OCSBadRequestException + * @throws OCSForbiddenException + * @throws OCSException + */ + public function destroy(int $id): DataResponse { + try { + $deleted = $this->manager->deleteOperation($id, $this->getScopeContext()); + return new DataResponse($deleted); + } catch (\UnexpectedValueException $e) { + throw new OCSBadRequestException($e->getMessage(), $e); + } catch (\DomainException $e) { + throw new OCSForbiddenException($e->getMessage(), $e); + } catch(DBALException $e) { + throw new OCSException('An internal error occurred', $e); + } + } +} diff --git a/apps/workflowengine/lib/Controller/GlobalWorkflowsController.php b/apps/workflowengine/lib/Controller/GlobalWorkflowsController.php index e4321b9c0b6..6d49c87b83e 100644 --- a/apps/workflowengine/lib/Controller/GlobalWorkflowsController.php +++ b/apps/workflowengine/lib/Controller/GlobalWorkflowsController.php @@ -24,88 +24,18 @@ declare(strict_types=1); namespace OCA\WorkflowEngine\Controller; -use OCA\WorkflowEngine\Manager; -use OCP\AppFramework\Http\DataResponse; -use OCP\AppFramework\OCS\OCSBadRequestException; -use OCP\AppFramework\OCSController; -use OCP\IRequest; +use OCA\WorkflowEngine\Helper\ScopeContext; +use OCP\WorkflowEngine\IManager; -class GlobalWorkflowsController extends OCSController { +class GlobalWorkflowsController extends AWorkflowController { - /** @var Manager */ - private $manager; + /** @var ScopeContext */ + private $scopeContext; - public function __construct( - $appName, - IRequest $request, - Manager $manager - ) { - parent::__construct($appName, $request); - - $this->manager = $manager; - } - - /** - * Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global?format=json" - */ - public function index(): DataResponse { - $operationsByClass = $this->manager->getAllOperations(); - - foreach ($operationsByClass as &$operations) { - foreach ($operations as &$operation) { - $operation = $this->manager->formatOperation($operation); - } - } - - return new DataResponse($operationsByClass); - } - - /** - * @throws OCSBadRequestException - * - * Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global/OCA\\Workflow_DocToPdf\\Operation?format=json" - */ - public function show(string $id): DataResponse { - // The ID corresponds to a class name - $operations = $this->manager->getOperations($id); - - foreach ($operations as &$operation) { - $operation = $this->manager->formatOperation($operation); + protected function getScopeContext(): ScopeContext { + if($this->scopeContext === null) { + $this->scopeContext = new ScopeContext(IManager::SCOPE_ADMIN); } - - return new DataResponse($operations); - } - - /** - * @throws OCSBadRequestException - */ - public function create(string $class, string $name, array $checks, string $operation): DataResponse { - try { - $operation = $this->manager->addOperation($class, $name, $checks, $operation); - $operation = $this->manager->formatOperation($operation); - return new DataResponse($operation); - } catch (\UnexpectedValueException $e) { - throw new OCSBadRequestException($e->getMessage(), $e); - } - } - - /** - * @throws OCSBadRequestException - */ - public function update(int $id, string $name, array $checks, string $operation): DataResponse { - try { - $operation = $this->manager->updateOperation($id, $name, $checks, $operation); - $operation = $this->manager->formatOperation($operation); - return new DataResponse($operation); - } catch (\UnexpectedValueException $e) { - throw new OCSBadRequestException($e->getMessage(), $e); - } - } - - /** - */ - public function destroy(int $id): DataResponse { - $deleted = $this->manager->deleteOperation((int) $id); - return new DataResponse($deleted); + return $this->scopeContext; } } diff --git a/apps/workflowengine/lib/Controller/UserWorkflowsController.php b/apps/workflowengine/lib/Controller/UserWorkflowsController.php new file mode 100644 index 00000000000..179e6b1ad11 --- /dev/null +++ b/apps/workflowengine/lib/Controller/UserWorkflowsController.php @@ -0,0 +1,117 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Controller; + +use OCA\WorkflowEngine\Helper\ScopeContext; +use OCA\WorkflowEngine\Manager; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSBadRequestException; +use OCP\AppFramework\OCS\OCSForbiddenException; +use OCP\IRequest; +use OCP\IUserSession; +use OCP\WorkflowEngine\IManager; + +class UserWorkflowsController extends AWorkflowController { + + /** @var IUserSession */ + private $session; + + /** @var ScopeContext */ + private $scopeContext; + + public function __construct( + $appName, + IRequest $request, + Manager $manager, + IUserSession $session + ) { + parent::__construct($appName, $request, $manager); + + $this->session = $session; + } + + /** + * Retrieve all configured workflow rules + * + * Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/user?format=json" + * + * @NoAdminRequired + * @throws OCSForbiddenException + */ + public function index(): DataResponse { + return parent::index(); + } + + /** + * @NoAdminRequired + * + * Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/user/OCA\\Workflow_DocToPdf\\Operation?format=json" + * @throws OCSForbiddenException + */ + public function show(string $id): DataResponse { + return parent::show($id); + } + + /** + * @NoAdminRequired + * @throws OCSBadRequestException + * @throws OCSForbiddenException + */ + public function create(string $class, string $name, array $checks, string $operation): DataResponse { + return parent::create($class, $name, $checks, $operation); + } + + /** + * @NoAdminRequired + * @throws OCSBadRequestException + * @throws OCSForbiddenException + */ + public function update(int $id, string $name, array $checks, string $operation): DataResponse { + return parent::update($id, $name, $checks, $operation); + } + + /** + * @NoAdminRequired + * @throws OCSForbiddenException + */ + public function destroy(int $id): DataResponse { + return parent::destroy($id); + } + + /** + * @throws OCSForbiddenException + */ + protected function getScopeContext(): ScopeContext { + if($this->scopeContext === null) { + $user = $this->session->getUser(); + if(!$user) { + throw new OCSForbiddenException('User not logged in'); + } + $this->scopeContext = new ScopeContext(IManager::SCOPE_USER, $user->getUID()); + } + return $this->scopeContext; + } + +} diff --git a/apps/workflowengine/lib/Helper/ScopeContext.php b/apps/workflowengine/lib/Helper/ScopeContext.php new file mode 100644 index 00000000000..fecc4db0ed7 --- /dev/null +++ b/apps/workflowengine/lib/Helper/ScopeContext.php @@ -0,0 +1,78 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Helper; + +use OCP\WorkflowEngine\IManager; + +class ScopeContext { + /** @var int */ + private $scope; + /** @var string */ + private $scopeId; + /** @var string */ + private $hash; + + public function __construct(int $scope, string $scopeId = null) { + $this->scope = $this->evaluateScope($scope); + $this->scopeId = $this->evaluateScopeId($scopeId); + } + + private function evaluateScope(int $scope): int { + if(in_array($scope, [IManager::SCOPE_ADMIN, IManager::SCOPE_USER], true)) { + return $scope; + } + throw new \InvalidArgumentException('Invalid scope'); + } + + private function evaluateScopeId(string $scopeId = null): string { + if($this->scope === IManager::SCOPE_USER + && trim((string)$scopeId) === '') + { + throw new \InvalidArgumentException('user scope requires a user id'); + } + return trim((string)$scopeId); + } + + /** + * @return int + */ + public function getScope(): int { + return $this->scope; + } + + /** + * @return string + */ + public function getScopeId(): string { + return $this->scopeId; + } + + public function getHash(): string { + if($this->hash === null) { + $this->hash = \hash('sha256', $this->getScope() . '::' . $this->getScopeId()); + } + return $this->hash; + } +} diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index 3bfedfbfbf4..ecf3f24bda1 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -23,7 +23,10 @@ namespace OCA\WorkflowEngine; use OC\Files\Storage\Wrapper\Jail; +use Doctrine\DBAL\DBALException; +use OC\Cache\CappedMemoryCache; use OCA\WorkflowEngine\Entity\File; +use OCA\WorkflowEngine\Helper\ScopeContext; use OCP\AppFramework\QueryException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Storage\IStorage; @@ -74,6 +77,10 @@ class Manager implements IManager, IEntityAware { /** @var ILogger */ protected $logger; + + /** @var CappedMemoryCache */ + protected $operationsByScope = []; + /** @var IUserSession */ protected $session; @@ -95,6 +102,7 @@ class Manager implements IManager, IEntityAware { $this->l = $l; $this->eventDispatcher = $eventDispatcher; $this->logger = $logger; + $this->operationsByScope = new CappedMemoryCache(64); $this->session = $session; } @@ -114,7 +122,16 @@ class Manager implements IManager, IEntityAware { * @inheritdoc */ public function getMatchingOperations($class, $returnFirstMatchingOperationOnly = true) { - $operations = $this->getOperations($class); + $scopes[] = new ScopeContext(IManager::SCOPE_ADMIN); + $user = $this->session->getUser(); + if($user !== null) { + $scopes[] = new ScopeContext(IManager::SCOPE_USER, $user->getUID()); + } + + $operations = []; + foreach ($scopes as $scope) { + $operations = array_merge($operations, $this->getOperations($class, $scope)); + } $matches = []; foreach ($operations as $operation) { @@ -160,19 +177,10 @@ class Manager implements IManager, IEntityAware { throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class'])); } } - public function getAllOperations(int $scope = IManager::SCOPE_ADMIN, string $scopeId = null): array { - if(!in_array($scope, [IManager::SCOPE_ADMIN, IManager::SCOPE_USER])) { - throw new \InvalidArgumentException('Provided value for scope is not supported'); + public function getAllOperations(ScopeContext $scopeContext): array { + if(isset($this->operations[$scopeContext->getHash()])) { + return $this->operations[$scopeContext->getHash()]; } - if($scope === IManager::SCOPE_USER && $scopeId === null) { - $user = $this->session->getUser(); - if($user === null) { - throw new \InvalidArgumentException('No user ID was provided'); - } - $scopeId = $user->getUID(); - } - - $this->operations = []; $query = $this->connection->getQueryBuilder(); @@ -181,48 +189,29 @@ class Manager implements IManager, IEntityAware { ->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id')) ->where($query->expr()->eq('s.type', $query->createParameter('scope'))); - if($scope === IManager::SCOPE_USER) { + if($scopeContext->getScope() === IManager::SCOPE_USER) { $query->andWhere($query->expr()->eq('s.value', $query->createParameter('scopeId'))); } - $query->setParameters(['scope' => $scope, 'scopeId' => $scopeId]); - + $query->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]); $result = $query->execute(); + $this->operations[$scopeContext->getHash()] = []; while ($row = $result->fetch()) { - if(!isset($this->operations[$row['class']])) { - $this->operations[$row['class']] = []; + if(!isset($this->operations[$scopeContext->getHash()][$row['class']])) { + $this->operations[$scopeContext->getHash()][$row['class']] = []; } - $this->operations[$row['class']][] = $row; + $this->operations[$scopeContext->getHash()][$row['class']][] = $row; } - return $this->operations; + return $this->operations[$scopeContext->getHash()]; } - - /** - * @param string $class - * @return array[] - */ - public function getOperations($class) { - if (isset($this->operations[$class])) { - return $this->operations[$class]; - } - - $query = $this->connection->getQueryBuilder(); - - $query->select('*') - ->from('flow_operations') - ->where($query->expr()->eq('class', $query->createNamedParameter($class))); - $result = $query->execute(); - - $this->operations[$class] = []; - while ($row = $result->fetch()) { - $this->operations[$class][] = $row; + public function getOperations(string $class, ScopeContext $scopeContext): array { + if (!isset($this->operations[$scopeContext->getHash()])) { + $this->getAllOperations($scopeContext); } - $result->closeCursor(); - - return $this->operations[$class]; + return $this->operations[$scopeContext->getHash()][$class] ?? []; } /** @@ -246,6 +235,20 @@ class Manager implements IManager, IEntityAware { throw new \UnexpectedValueException($this->l->t('Operation #%s does not exist', [$id])); } + protected function insertOperation(string $class, string $name, array $checkIds, string $operation): int { + $query = $this->connection->getQueryBuilder(); + $query->insert('flow_operations') + ->values([ + 'class' => $query->createNamedParameter($class), + 'name' => $query->createNamedParameter($name), + 'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))), + 'operation' => $query->createNamedParameter($operation), + ]); + $query->execute(); + + return $query->getLastInsertId(); + } + /** * @param string $class * @param string $name @@ -253,29 +256,58 @@ class Manager implements IManager, IEntityAware { * @param string $operation * @return array The added operation * @throws \UnexpectedValueException + * @throws DBALException */ - public function addOperation($class, $name, array $checks, $operation) { + public function addOperation($class, $name, array $checks, $operation, ScopeContext $scope) { $this->validateOperation($class, $name, $checks, $operation); - $checkIds = []; - foreach ($checks as $check) { - $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']); - } + $this->connection->beginTransaction(); - $query = $this->connection->getQueryBuilder(); - $query->insert('flow_operations') - ->values([ - 'class' => $query->createNamedParameter($class), - 'name' => $query->createNamedParameter($name), - 'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))), - 'operation' => $query->createNamedParameter($operation), - ]); - $query->execute(); + try { + $checkIds = []; + foreach ($checks as $check) { + $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']); + } + + $id = $this->insertOperation($class, $name, $checkIds, $operation); + $this->addScope($id, $scope); + + $this->connection->commit(); + } catch (DBALException $e) { + $this->connection->rollBack(); + throw $e; + } - $id = $query->getLastInsertId(); return $this->getOperation($id); } + protected function canModify(int $id, ScopeContext $scopeContext):bool { + if(isset($this->operationsByScope[$scopeContext->getHash()])) { + return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true); + } + + $qb = $this->connection->getQueryBuilder(); + $qb = $qb->select('o.id') + ->from('flow_operations', 'o') + ->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id')) + ->where($qb->expr()->eq('s.type', $qb->createParameter('scope'))); + + if($scopeContext->getScope() !== IManager::SCOPE_ADMIN) { + $qb->where($qb->expr()->eq('s.value', $qb->createParameter('scopeId'))); + } + + $qb->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]); + $result = $qb->execute(); + + $this->operationsByScope[$scopeContext->getHash()] = []; + while($opId = $result->fetchColumn(0)) { + $this->operationsByScope[$scopeContext->getHash()][] = (int)$opId; + } + $result->closeCursor(); + + return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true); + } + /** * @param int $id * @param string $name @@ -283,23 +315,36 @@ class Manager implements IManager, IEntityAware { * @param string $operation * @return array The updated operation * @throws \UnexpectedValueException + * @throws \DomainException + * @throws DBALException */ - public function updateOperation($id, $name, array $checks, $operation) { + public function updateOperation($id, $name, array $checks, $operation, ScopeContext $scopeContext): 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); $checkIds = []; - foreach ($checks as $check) { - $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']); - } + try { + $this->connection->beginTransaction(); + foreach ($checks as $check) { + $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']); + } - $query = $this->connection->getQueryBuilder(); - $query->update('flow_operations') - ->set('name', $query->createNamedParameter($name)) - ->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds)))) - ->set('operation', $query->createNamedParameter($operation)) - ->where($query->expr()->eq('id', $query->createNamedParameter($id))); - $query->execute(); + $query = $this->connection->getQueryBuilder(); + $query->update('flow_operations') + ->set('name', $query->createNamedParameter($name)) + ->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds)))) + ->set('operation', $query->createNamedParameter($operation)) + ->where($query->expr()->eq('id', $query->createNamedParameter($id))); + $query->execute(); + $this->connection->commit(); + } catch (DBALException $e) { + $this->connection->rollBack(); + throw $e; + } + unset($this->operations[$scopeContext->getHash()]); return $this->getOperation($id); } @@ -308,12 +353,36 @@ class Manager implements IManager, IEntityAware { * @param int $id * @return bool * @throws \UnexpectedValueException + * @throws DBALException + * @throws \DomainException */ - public function deleteOperation($id) { + public function deleteOperation($id, ScopeContext $scopeContext) { + if(!$this->canModify($id, $scopeContext)) { + throw new \DomainException('Target operation not within scope'); + }; $query = $this->connection->getQueryBuilder(); - $query->delete('flow_operations') - ->where($query->expr()->eq('id', $query->createNamedParameter($id))); - return (bool) $query->execute(); + try { + $this->connection->beginTransaction(); + $result = (bool)$query->delete('flow_operations') + ->where($query->expr()->eq('id', $query->createNamedParameter($id))) + ->execute(); + if($result) { + $qb = $this->connection->getQueryBuilder(); + $result &= (bool)$qb->delete('flow_operations_scope') + ->where($qb->expr()->eq('operation_id', $query->createNamedParameter($id))) + ->execute(); + } + $this->connection->commit(); + } catch (DBALException $e) { + $this->connection->rollBack(); + throw $e; + } + + if(isset($this->operations[$scopeContext->getHash()])) { + unset($this->operations[$scopeContext->getHash()]); + } + + return $result; } /** @@ -427,6 +496,18 @@ class Manager implements IManager, IEntityAware { return $query->getLastInsertId(); } + protected function addScope(int $operationId, ScopeContext $scope): void { + $query = $this->connection->getQueryBuilder(); + + $insertQuery = $query->insert('flow_operations_scope'); + $insertQuery->values([ + 'operation_id' => $query->createNamedParameter($operationId), + 'type' => $query->createNamedParameter($scope->getScope()), + 'value' => $query->createNamedParameter($scope->getScopeId()), + ]); + $insertQuery->execute(); + } + public function formatOperation(array $operation): array { $checkIds = json_decode($operation['checks'], true); $checks = $this->getChecks($checkIds); diff --git a/apps/workflowengine/tests/ManagerTest.php b/apps/workflowengine/tests/ManagerTest.php index ecfc92713a0..b25adb96f15 100644 --- a/apps/workflowengine/tests/ManagerTest.php +++ b/apps/workflowengine/tests/ManagerTest.php @@ -23,12 +23,16 @@ namespace OCA\WorkflowEngine\Tests; use OCA\WorkflowEngine\Entity\File; +use OCA\WorkflowEngine\Helper\ScopeContext; use OCA\WorkflowEngine\Manager; use OCP\IDBConnection; use OCP\IL10N; use OCP\ILogger; use OCP\IServerContainer; +use OCP\WorkflowEngine\ICheck; use OCP\WorkflowEngine\IEntity; +use OCP\WorkflowEngine\IManager; +use OCP\WorkflowEngine\IOperation; use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Test\TestCase; @@ -74,18 +78,38 @@ class ManagerTest extends TestCase { $this->eventDispatcher, $this->logger ); - $this->clearChecks(); + $this->clearTables(); } protected function tearDown() { - $this->clearChecks(); + $this->clearTables(); parent::tearDown(); } - public function clearChecks() { + /** + * @return MockObject|ScopeContext + */ + protected function buildScope(string $scopeId = null): MockObject { + $scopeContext = $this->createMock(ScopeContext::class); + $scopeContext->expects($this->any()) + ->method('getScope') + ->willReturn($scopeId ? IManager::SCOPE_USER : IManager::SCOPE_ADMIN); + $scopeContext->expects($this->any()) + ->method('getScopeId') + ->willReturn($scopeId ?? ''); + $scopeContext->expects($this->any()) + ->method('getHash') + ->willReturn(md5($scopeId ?? '')); + + return $scopeContext; + } + + public function clearTables() { $query = $this->db->getQueryBuilder(); - $query->delete('flow_checks') - ->execute(); + foreach(['flow_checks', 'flow_operations', 'flow_operations_scope'] as $table) { + $query->delete($table) + ->execute(); + } } public function testChecks() { @@ -109,6 +133,221 @@ class ManagerTest extends TestCase { $this->assertArrayHasKey($check2, $data); } + public function testScope() { + $adminScope = $this->buildScope(); + $userScope = $this->buildScope('jackie'); + + $opId1 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestOp', 'Test01', [11, 22], 'foo'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]); + + $opId2 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestOp', 'Test02', [33, 22], 'bar'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]); + $opId3 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestOp', 'Test03', [11, 44], 'foobar'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId3, $userScope]); + + $this->assertTrue($this->invokePrivate($this->manager, 'canModify', [$opId1, $adminScope])); + $this->assertFalse($this->invokePrivate($this->manager, 'canModify', [$opId2, $adminScope])); + $this->assertFalse($this->invokePrivate($this->manager, 'canModify', [$opId3, $adminScope])); + + $this->assertFalse($this->invokePrivate($this->manager, 'canModify', [$opId1, $userScope])); + $this->assertTrue($this->invokePrivate($this->manager, 'canModify', [$opId2, $userScope])); + $this->assertTrue($this->invokePrivate($this->manager, 'canModify', [$opId3, $userScope])); + } + + public function testGetAllOperations() { + $adminScope = $this->buildScope(); + $userScope = $this->buildScope('jackie'); + + $opId1 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]); + + $opId2 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]); + $opId3 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestUserOp', 'Test03', [11, 44], 'foobar'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId3, $userScope]); + + $adminOps = $this->manager->getAllOperations($adminScope); + $userOps = $this->manager->getAllOperations($userScope); + + $this->assertSame(1, count($adminOps)); + $this->assertTrue(array_key_exists('OCA\WFE\TestAdminOp', $adminOps)); + $this->assertFalse(array_key_exists('OCA\WFE\TestUserOp', $adminOps)); + + $this->assertSame(1, count($userOps)); + $this->assertFalse(array_key_exists('OCA\WFE\TestAdminOp', $userOps)); + $this->assertTrue(array_key_exists('OCA\WFE\TestUserOp', $userOps)); + $this->assertSame(2, count($userOps['OCA\WFE\TestUserOp'])); + } + + public function testGetOperations() { + $adminScope = $this->buildScope(); + $userScope = $this->buildScope('jackie'); + + $opId1 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestOp', 'Test01', [11, 22], 'foo'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]); + $opId4 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\OtherTestOp', 'Test04', [5], 'foo'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId4, $adminScope]); + + $opId2 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestOp', 'Test02', [33, 22], 'bar'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]); + $opId3 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestOp', 'Test03', [11, 44], 'foobar'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId3, $userScope]); + $opId5 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\OtherTestOp', 'Test05', [5], 'foobar'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId5, $userScope]); + + $adminOps = $this->manager->getOperations('OCA\WFE\TestOp', $adminScope); + $userOps = $this->manager->getOperations('OCA\WFE\TestOp', $userScope); + + $this->assertSame(1, count($adminOps)); + array_walk($adminOps, function ($op) { + $this->assertTrue($op['class'] === 'OCA\WFE\TestOp'); + }); + + $this->assertSame(2, count($userOps)); + array_walk($userOps, function ($op) { + $this->assertTrue($op['class'] === 'OCA\WFE\TestOp'); + }); + + } + + public function testUpdateOperation() { + $adminScope = $this->buildScope(); + $userScope = $this->buildScope('jackie'); + + $this->container->expects($this->any()) + ->method('query') + ->willReturnCallback(function ($class) { + if(substr($class, -2) === 'Op') { + return $this->createMock(IOperation::class); + } + return $this->createMock(ICheck::class); + }); + + $opId1 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]); + + $opId2 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]); + + $check1 = ['class' => 'OCA\WFE\C22', 'operator' => 'eq', 'value' => 'asdf']; + $check2 = ['class' => 'OCA\WFE\C33', 'operator' => 'eq', 'value' => 23456]; + + /** @noinspection PhpUnhandledExceptionInspection */ + $op = $this->manager->updateOperation($opId1, 'Test01a', [$check1, $check2], 'foohur', $adminScope); + $this->assertSame('Test01a', $op['name']); + $this->assertSame('foohur', $op['operation']); + + /** @noinspection PhpUnhandledExceptionInspection */ + $op = $this->manager->updateOperation($opId2, 'Test02a', [$check1], 'barfoo', $userScope); + $this->assertSame('Test02a', $op['name']); + $this->assertSame('barfoo', $op['operation']); + + foreach([[$adminScope, $opId2], [$userScope, $opId1]] as $run) { + try { + /** @noinspection PhpUnhandledExceptionInspection */ + $this->manager->updateOperation($run[1], 'Evil', [$check2], 'hackx0r', $run[0]); + $this->assertTrue(false, 'DomainException not thrown'); + } catch (\DomainException $e) { + $this->assertTrue(true); + } + } + } + + public function testDeleteOperation() { + $adminScope = $this->buildScope(); + $userScope = $this->buildScope('jackie'); + + $opId1 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]); + + $opId2 = $this->invokePrivate( + $this->manager, + 'insertOperation', + ['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar'] + ); + $this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]); + + foreach([[$adminScope, $opId2], [$userScope, $opId1]] as $run) { + try { + /** @noinspection PhpUnhandledExceptionInspection */ + $this->manager->deleteOperation($run[1], $run[0]); + $this->assertTrue(false, 'DomainException not thrown'); + } catch (\Exception $e) { + $this->assertInstanceOf(\DomainException::class, $e); + } + } + + /** @noinspection PhpUnhandledExceptionInspection */ + $this->manager->deleteOperation($opId1, $adminScope); + /** @noinspection PhpUnhandledExceptionInspection */ + $this->manager->deleteOperation($opId2, $userScope); + + foreach([$opId1, $opId2] as $opId) { + try { + $this->invokePrivate($this->manager, 'getOperation', [$opId]); + $this->assertTrue(false, 'UnexpectedValueException not thrown'); + } catch(\Exception $e) { + $this->assertInstanceOf(\UnexpectedValueException::class, $e); + } + } + } + public function testGetEntitiesListBuildInOnly() { $fileEntityMock = $this->createMock(File::class); From 2288363b0fbf86dc5a16b6c3e680095006317a68 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 22 Aug 2019 11:00:59 +0200 Subject: [PATCH 07/68] add missing @since annotations Signed-off-by: Arthur Schiwon --- lib/public/WorkflowEngine/IEntityAware.php | 8 +++++++- lib/public/WorkflowEngine/IEntityEvent.php | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/public/WorkflowEngine/IEntityAware.php b/lib/public/WorkflowEngine/IEntityAware.php index 2ef74d2b115..5ef5066f9ad 100644 --- a/lib/public/WorkflowEngine/IEntityAware.php +++ b/lib/public/WorkflowEngine/IEntityAware.php @@ -24,7 +24,13 @@ declare(strict_types=1); namespace OCP\WorkflowEngine; - +/** + * Interface IEntityAware + * + * @package OCP\WorkflowEngine + * + * @since 18.0.0 + */ interface IEntityAware { /** * @param object $entity diff --git a/lib/public/WorkflowEngine/IEntityEvent.php b/lib/public/WorkflowEngine/IEntityEvent.php index 89186e808ed..8baa0573fa8 100644 --- a/lib/public/WorkflowEngine/IEntityEvent.php +++ b/lib/public/WorkflowEngine/IEntityEvent.php @@ -30,6 +30,8 @@ namespace OCP\WorkflowEngine; * represents an entitiy event that is dispatched via EventDispatcher * * @package OCP\WorkflowEngine + * + * @since 18.0.0 */ interface IEntityEvent { /** From 1c67357db8432174c2a80bbb9c729f4ead44ca0d Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 22 Aug 2019 17:34:27 +0200 Subject: [PATCH 08/68] section and settings for users Signed-off-by: Arthur Schiwon --- apps/workflowengine/appinfo/info.xml | 3 + .../composer/composer/autoload_classmap.php | 2 + .../composer/composer/autoload_static.php | 2 + apps/workflowengine/lib/Settings/Admin.php | 100 +----------------- apps/workflowengine/lib/Settings/Personal.php | 32 ++++++ apps/workflowengine/lib/Settings/Section.php | 2 +- .../templates/{admin.php => settings.php} | 0 7 files changed, 44 insertions(+), 97 deletions(-) create mode 100644 apps/workflowengine/lib/Settings/Personal.php rename apps/workflowengine/templates/{admin.php => settings.php} (100%) diff --git a/apps/workflowengine/appinfo/info.xml b/apps/workflowengine/appinfo/info.xml index ddb20538280..e535ffa1432 100644 --- a/apps/workflowengine/appinfo/info.xml +++ b/apps/workflowengine/appinfo/info.xml @@ -36,6 +36,9 @@ + OCA\WorkflowEngine\Settings\Admin OCA\WorkflowEngine\Settings\Section + OCA\WorkflowEngine\Settings\Admin + OCA\WorkflowEngine\Settings\Section diff --git a/apps/workflowengine/composer/composer/autoload_classmap.php b/apps/workflowengine/composer/composer/autoload_classmap.php index 81047b10614..637f1a8abff 100644 --- a/apps/workflowengine/composer/composer/autoload_classmap.php +++ b/apps/workflowengine/composer/composer/autoload_classmap.php @@ -30,6 +30,8 @@ return array( 'OCA\\WorkflowEngine\\Manager' => $baseDir . '/../lib/Manager.php', 'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => $baseDir . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php', 'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => $baseDir . '/../lib/Migration/Version2019Date20190808074233.php', + 'OCA\\WorkflowEngine\\Settings\\ASettings' => $baseDir . '/../lib/Settings/ASettings.php', 'OCA\\WorkflowEngine\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php', + 'OCA\\WorkflowEngine\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php', 'OCA\\WorkflowEngine\\Settings\\Section' => $baseDir . '/../lib/Settings/Section.php', ); diff --git a/apps/workflowengine/composer/composer/autoload_static.php b/apps/workflowengine/composer/composer/autoload_static.php index d016377ea54..edf3f3b0518 100644 --- a/apps/workflowengine/composer/composer/autoload_static.php +++ b/apps/workflowengine/composer/composer/autoload_static.php @@ -45,7 +45,9 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Manager' => __DIR__ . '/..' . '/../lib/Manager.php', 'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => __DIR__ . '/..' . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php', 'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => __DIR__ . '/..' . '/../lib/Migration/Version2019Date20190808074233.php', + 'OCA\\WorkflowEngine\\Settings\\ASettings' => __DIR__ . '/..' . '/../lib/Settings/ASettings.php', 'OCA\\WorkflowEngine\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php', + 'OCA\\WorkflowEngine\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php', 'OCA\\WorkflowEngine\\Settings\\Section' => __DIR__ . '/..' . '/../lib/Settings/Section.php', ); diff --git a/apps/workflowengine/lib/Settings/Admin.php b/apps/workflowengine/lib/Settings/Admin.php index 8ffd8533d3b..39932d5f1f2 100644 --- a/apps/workflowengine/lib/Settings/Admin.php +++ b/apps/workflowengine/lib/Settings/Admin.php @@ -24,103 +24,11 @@ declare(strict_types=1); namespace OCA\WorkflowEngine\Settings; -use OCA\WorkflowEngine\AppInfo\Application; -use OCA\WorkflowEngine\Manager; -use OCP\AppFramework\Http\TemplateResponse; -use OCP\IInitialStateService; -use OCP\IL10N; -use OCP\Settings\ISettings; -use OCP\WorkflowEngine\IEntity; -use OCP\WorkflowEngine\IEntityEvent; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; +class Admin extends ASettings { -class Admin implements ISettings { - - /** @var IL10N */ - private $l10n; - - /** @var string */ - private $appName; - - /** @var EventDispatcherInterface */ - private $eventDispatcher; - - /** @var Manager */ - private $manager; - - /** @var IInitialStateService */ - private $initialStateService; - - /** - * @param string $appName - * @param IL10N $l - * @param EventDispatcherInterface $eventDispatcher - */ - public function __construct( - $appName, - IL10N $l, - EventDispatcherInterface $eventDispatcher, - Manager $manager, - IInitialStateService $initialStateService - ) { - $this->appName = $appName; - $this->l10n = $l; - $this->eventDispatcher = $eventDispatcher; - $this->manager = $manager; - $this->initialStateService = $initialStateService; + function isAdmin(): bool { + return true; } +} - /** - * @return TemplateResponse - */ - public function getForm() { - $this->eventDispatcher->dispatch('OCP\WorkflowEngine::loadAdditionalSettingScripts'); - - $entities = $this->manager->getEntitiesList(); - - $this->initialStateService->provideInitialState( - Application::APP_ID, - 'entities', - $this->entitiesToArray($entities) - ); - - return new TemplateResponse(Application::APP_ID, 'admin', [], 'blank'); - } - - /** - * @return string the section ID, e.g. 'sharing' - */ - public function getSection() { - return 'workflow'; - } - - /** - * @return int whether the form should be rather on the top or bottom of - * the admin section. The forms are arranged in ascending order of the - * priority values. It is required to return a value between 0 and 100. - * - * E.g.: 70 - */ - public function getPriority() { - return 0; - } - - private function entitiesToArray(array $entities) { - return array_map(function (IEntity $entity) { - $events = array_map(function(IEntityEvent $entityEvent) { - return [ - 'eventName' => $entityEvent->getEventName(), - 'displayName' => $entityEvent->getDisplayName() - ]; - }, $entity->getEvents()); - - return [ - 'id' => $entity->getId(), - 'icon' => $entity->getIcon(), - 'name' => $entity->getName(), - 'events' => $events, - ]; - }, $entities); - } -} diff --git a/apps/workflowengine/lib/Settings/Personal.php b/apps/workflowengine/lib/Settings/Personal.php new file mode 100644 index 00000000000..d6ed220577f --- /dev/null +++ b/apps/workflowengine/lib/Settings/Personal.php @@ -0,0 +1,32 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Settings; + +class Personal extends ASettings { + + function isAdmin(): bool { + return false; + } +} diff --git a/apps/workflowengine/lib/Settings/Section.php b/apps/workflowengine/lib/Settings/Section.php index 0ed6b222ffe..031e9438d9a 100644 --- a/apps/workflowengine/lib/Settings/Section.php +++ b/apps/workflowengine/lib/Settings/Section.php @@ -53,7 +53,7 @@ class Section implements IIconSection { * {@inheritdoc} */ public function getName() { - return $this->l->t('Tag management'); + return $this->l->t('Workflows'); } /** diff --git a/apps/workflowengine/templates/admin.php b/apps/workflowengine/templates/settings.php similarity index 100% rename from apps/workflowengine/templates/admin.php rename to apps/workflowengine/templates/settings.php From ec36c0ae80ea50d460f2c15c16e7d9de15cafd40 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 23 Aug 2019 16:56:24 +0200 Subject: [PATCH 09/68] add operator interfaces / API Signed-off-by: Arthur Schiwon --- apps/workflowengine/lib/Manager.php | 33 +++- .../workflowengine/lib/Settings/ASettings.php | 158 ++++++++++++++++++ apps/workflowengine/lib/Settings/Admin.php | 6 +- apps/workflowengine/lib/Settings/Personal.php | 6 +- lib/composer/composer/autoload_classmap.php | 3 + lib/composer/composer/autoload_static.php | 3 + .../WorkflowEngine/IComplexOperator.php | 43 +++++ lib/public/WorkflowEngine/IManager.php | 8 + lib/public/WorkflowEngine/IOperator.php | 91 ++++++++++ .../WorkflowEngine/ISpecificOperator.php | 50 ++++++ 10 files changed, 396 insertions(+), 5 deletions(-) create mode 100644 apps/workflowengine/lib/Settings/ASettings.php create mode 100644 lib/public/WorkflowEngine/IComplexOperator.php create mode 100644 lib/public/WorkflowEngine/IOperator.php create mode 100644 lib/public/WorkflowEngine/ISpecificOperator.php diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index ecf3f24bda1..d19aa31547a 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -40,6 +40,7 @@ use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IEntityAware; use OCP\WorkflowEngine\IManager; use OCP\WorkflowEngine\IOperation; +use OCP\WorkflowEngine\IOperator; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -75,6 +76,9 @@ class Manager implements IManager, IEntityAware { /** @var IEntity[] */ protected $registeredEntities = []; + /** @var IOperator[] */ + protected $registeredOperators = []; + /** @var ILogger */ protected $logger; @@ -545,12 +549,21 @@ class Manager implements IManager, IEntityAware { /** * @return IEntity[] */ - public function getEntitiesList() { + public function getEntitiesList(): array { $this->eventDispatcher->dispatch('OCP\WorkflowEngine::registerEntities', new GenericEvent($this)); return array_merge($this->getBuildInEntities(), $this->registeredEntities); } + /** + * @return IOperator[] + */ + public function getOperatorList(): array { + $this->eventDispatcher->dispatch('OCP\WorkflowEngine::registerOperators', new GenericEvent($this)); + + return array_merge($this->getBuildInOperators(), $this->registeredOperators); + } + /** * Listen to 'OCP/WorkflowEngine::registerEntities' at the EventDispatcher * for registering your entities @@ -561,6 +574,10 @@ class Manager implements IManager, IEntityAware { $this->registeredEntities[$entity->getId()] = $entity; } + public function registerOperator(IOperator $operator): void { + $this->registeredOperators[$operator->getId()] = $operator; + } + /** * @return IEntity[] */ @@ -574,4 +591,18 @@ class Manager implements IManager, IEntityAware { return []; } } + + /** + * @return IOperator[] + */ + protected function getBuildInOperators(): array { + try { + return [ + // None yet + ]; + } catch (QueryException $e) { + $this->logger->logException($e); + return []; + } + } } diff --git a/apps/workflowengine/lib/Settings/ASettings.php b/apps/workflowengine/lib/Settings/ASettings.php new file mode 100644 index 00000000000..78a23d924c7 --- /dev/null +++ b/apps/workflowengine/lib/Settings/ASettings.php @@ -0,0 +1,158 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Settings; + +use OCA\WorkflowEngine\AppInfo\Application; +use OCA\WorkflowEngine\Manager; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\IInitialStateService; +use OCP\IL10N; +use OCP\Settings\ISettings; +use OCP\WorkflowEngine\IComplexOperator; +use OCP\WorkflowEngine\IEntity; +use OCP\WorkflowEngine\IEntityEvent; +use OCP\WorkflowEngine\IOperator; +use OCP\WorkflowEngine\ISpecificOperator; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +abstract class ASettings implements ISettings { + /** @var IL10N */ + private $l10n; + + /** @var string */ + private $appName; + + /** @var EventDispatcherInterface */ + private $eventDispatcher; + + /** @var Manager */ + private $manager; + + /** @var IInitialStateService */ + private $initialStateService; + + /** + * @param string $appName + * @param IL10N $l + * @param EventDispatcherInterface $eventDispatcher + */ + public function __construct( + $appName, + IL10N $l, + EventDispatcherInterface $eventDispatcher, + Manager $manager, + IInitialStateService $initialStateService + ) { + $this->appName = $appName; + $this->l10n = $l; + $this->eventDispatcher = $eventDispatcher; + $this->manager = $manager; + $this->initialStateService = $initialStateService; + } + + abstract function getScope(): int; + + /** + * @return TemplateResponse + */ + public function getForm() { + $this->eventDispatcher->dispatch('OCP\WorkflowEngine::loadAdditionalSettingScripts'); + + $entities = $this->manager->getEntitiesList(); + $this->initialStateService->provideInitialState( + Application::APP_ID, + 'entities', + $this->entitiesToArray($entities) + ); + + $operators = $this->manager->getOperatorList(); + $this->initialStateService->provideInitialState( + Application::APP_ID, + 'operators', + $this->operatorsToArray($operators) + ); + + $this->initialStateService->provideInitialState( + Application::APP_ID, + 'scope', + $this->getScope() + ); + + return new TemplateResponse(Application::APP_ID, 'settings', [], 'blank'); + } + + /** + * @return string the section ID, e.g. 'sharing' + */ + public function getSection() { + return 'workflow'; + } + + /** + * @return int whether the form should be rather on the top or bottom of + * the admin section. The forms are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * + * E.g.: 70 + */ + public function getPriority() { + return 0; + } + + private function entitiesToArray(array $entities) { + return array_map(function (IEntity $entity) { + $events = array_map(function(IEntityEvent $entityEvent) { + return [ + 'eventName' => $entityEvent->getEventName(), + 'displayName' => $entityEvent->getDisplayName() + ]; + }, $entity->getEvents()); + + return [ + 'id' => $entity->getId(), + 'icon' => $entity->getIcon(), + 'name' => $entity->getName(), + 'events' => $events, + ]; + }, $entities); + } + + private function operatorsToArray(array $operators) { + $operators = array_filter($operators, function(IOperator $operator) { + return $operator->isAvailableForScope($this->getScope()); + }); + + return array_map(function (IOperator $operator) { + return [ + 'id' => $operator->getId(), + 'icon' => $operator->getIcon(), + 'name' => $operator->getDisplayName(), + 'description' => $operator->getDescription(), + 'fixedEntity' => $operator instanceof ISpecificOperator ? $operator->getEntityId() : '', + 'isComplex' => $operator instanceof IComplexOperator, + ]; + }, $operators); + } +} diff --git a/apps/workflowengine/lib/Settings/Admin.php b/apps/workflowengine/lib/Settings/Admin.php index 39932d5f1f2..20ea8ad43c4 100644 --- a/apps/workflowengine/lib/Settings/Admin.php +++ b/apps/workflowengine/lib/Settings/Admin.php @@ -24,10 +24,12 @@ declare(strict_types=1); namespace OCA\WorkflowEngine\Settings; +use OCP\WorkflowEngine\IManager; + class Admin extends ASettings { - function isAdmin(): bool { - return true; + function getScope(): int { + return IManager::SCOPE_ADMIN; } } diff --git a/apps/workflowengine/lib/Settings/Personal.php b/apps/workflowengine/lib/Settings/Personal.php index d6ed220577f..9c23a250355 100644 --- a/apps/workflowengine/lib/Settings/Personal.php +++ b/apps/workflowengine/lib/Settings/Personal.php @@ -24,9 +24,11 @@ declare(strict_types=1); namespace OCA\WorkflowEngine\Settings; +use OCP\WorkflowEngine\IManager; + class Personal extends ASettings { - function isAdmin(): bool { - return false; + function getScope(): int { + return IManager::SCOPE_USER; } } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 2c76674ee52..76322993b48 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -439,11 +439,14 @@ return array( 'OCP\\User\\Backend\\ISetPasswordBackend' => $baseDir . '/lib/public/User/Backend/ISetPasswordBackend.php', 'OCP\\Util' => $baseDir . '/lib/public/Util.php', 'OCP\\WorkflowEngine\\ICheck' => $baseDir . '/lib/public/WorkflowEngine/ICheck.php', + 'OCP\\WorkflowEngine\\IComplexOperator' => $baseDir . '/lib/public/WorkflowEngine/IComplexOperator.php', 'OCP\\WorkflowEngine\\IEntity' => $baseDir . '/lib/public/WorkflowEngine/IEntity.php', 'OCP\\WorkflowEngine\\IEntityAware' => $baseDir . '/lib/public/WorkflowEngine/IEntityAware.php', 'OCP\\WorkflowEngine\\IEntityEvent' => $baseDir . '/lib/public/WorkflowEngine/IEntityEvent.php', 'OCP\\WorkflowEngine\\IManager' => $baseDir . '/lib/public/WorkflowEngine/IManager.php', 'OCP\\WorkflowEngine\\IOperation' => $baseDir . '/lib/public/WorkflowEngine/IOperation.php', + 'OCP\\WorkflowEngine\\IOperator' => $baseDir . '/lib/public/WorkflowEngine/IOperator.php', + 'OCP\\WorkflowEngine\\ISpecificOperator' => $baseDir . '/lib/public/WorkflowEngine/ISpecificOperator.php', 'OC\\Accounts\\Account' => $baseDir . '/lib/private/Accounts/Account.php', 'OC\\Accounts\\AccountManager' => $baseDir . '/lib/private/Accounts/AccountManager.php', 'OC\\Accounts\\AccountProperty' => $baseDir . '/lib/private/Accounts/AccountProperty.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 651364d06c1..f164d0b15bd 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -473,11 +473,14 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\User\\Backend\\ISetPasswordBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISetPasswordBackend.php', 'OCP\\Util' => __DIR__ . '/../../..' . '/lib/public/Util.php', 'OCP\\WorkflowEngine\\ICheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ICheck.php', + 'OCP\\WorkflowEngine\\IComplexOperator' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IComplexOperator.php', 'OCP\\WorkflowEngine\\IEntity' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntity.php', 'OCP\\WorkflowEngine\\IEntityAware' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityAware.php', 'OCP\\WorkflowEngine\\IEntityEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityEvent.php', 'OCP\\WorkflowEngine\\IManager' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IManager.php', 'OCP\\WorkflowEngine\\IOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IOperation.php', + 'OCP\\WorkflowEngine\\IOperator' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IOperator.php', + 'OCP\\WorkflowEngine\\ISpecificOperator' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ISpecificOperator.php', 'OC\\Accounts\\Account' => __DIR__ . '/../../..' . '/lib/private/Accounts/Account.php', 'OC\\Accounts\\AccountManager' => __DIR__ . '/../../..' . '/lib/private/Accounts/AccountManager.php', 'OC\\Accounts\\AccountProperty' => __DIR__ . '/../../..' . '/lib/private/Accounts/AccountProperty.php', diff --git a/lib/public/WorkflowEngine/IComplexOperator.php b/lib/public/WorkflowEngine/IComplexOperator.php new file mode 100644 index 00000000000..ee2d3d0a183 --- /dev/null +++ b/lib/public/WorkflowEngine/IComplexOperator.php @@ -0,0 +1,43 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\WorkflowEngine; + +/** + * Interface IComplexOperator + * + * This interface represents an operator that is less generic and indicates + * that some of the tasks it does itself instead of relying on the engine. + * This includes: + * + * * registering listeners – the implementing app needs to ensure that the + * business logic registers listeners to the events it listens to. For example + * when direct storage access is required, adding a wrapper or listening to + * a specific one is required over usual file events. + * + * @package OCP\WorkflowEngine + * + * @sincee 18.0.0 + */ +interface IComplexOperator extends IOperator { } diff --git a/lib/public/WorkflowEngine/IManager.php b/lib/public/WorkflowEngine/IManager.php index c05459e1fb4..6d4bacc8e17 100644 --- a/lib/public/WorkflowEngine/IManager.php +++ b/lib/public/WorkflowEngine/IManager.php @@ -59,4 +59,12 @@ interface IManager { * @since 18.0.0 */ public function registerEntity(IEntity $entity): void; + + /** + * Listen to 'OCP/WorkflowEngine::registerOperators' at the EventDispatcher + * for registering your operators + * + * @since 18.0.0 + */ + public function registerOperator(IOperator $operator): void; } diff --git a/lib/public/WorkflowEngine/IOperator.php b/lib/public/WorkflowEngine/IOperator.php new file mode 100644 index 00000000000..70c80d98c45 --- /dev/null +++ b/lib/public/WorkflowEngine/IOperator.php @@ -0,0 +1,91 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\WorkflowEngine; + +/** + * @since 18.0.0 + */ +interface IOperator { + /** + * returns the unique identity of the operator + * + * It is recommended to use the namespaced class name of the IOperator + * implementation. Especially workflow applications released before + * Nextcloud 18 should chose this as id for compatibility. + * + * Example: OCA\FilesAutomatedTagging\Operation + * + * @since 18.0.0 + */ + public function getId(): string; + + /** + * returns a translated name to be presented in the web interface + * + * Example: "Automated tagging" (en), "Aŭtomata etikedado" (eo) + * + * @since 18.0.0 + */ + public function getDisplayName(): string; + + /** + * returns a translated, descriptive text to be presented in the web interface. + * + * It should be short and precise. + * + * Example: "Tag based automatic deletion of files after a given time." (en) + * + * @since 18.0.0 + */ + public function getDescription(): string; + + /** + * returns the URL to the icon of the operator for display in the web interface. + * + * Usually, the implementation would utilize the `imagePath()` method of the + * `\OCP\IURLGenerator` instance and simply return its result. + * + * Example implementation: return $this->urlGenerator->imagePath('myApp', 'cat.svg'); + * + * @since 18.0.0 + */ + public function getIcon(): string; + + /** + * returns whether the operation can be used in the requested scope. + * + * Scope IDs are defined as constants in OCP\WorkflowEngine\IManager. At + * time of writing these are SCOPE_ADMIN and SCOPE_USER. + * + * For possibly unknown future scopes the recommended behaviour is: if + * user scope is permitted, the default behaviour should return `true`, + * otherwise `false`. + * + * @since 18.0.0 + */ + public function isAvailableForScope(int $scope): bool; + + +} diff --git a/lib/public/WorkflowEngine/ISpecificOperator.php b/lib/public/WorkflowEngine/ISpecificOperator.php new file mode 100644 index 00000000000..a5ae9fc1841 --- /dev/null +++ b/lib/public/WorkflowEngine/ISpecificOperator.php @@ -0,0 +1,50 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\WorkflowEngine; + +/** + * Interface ISpecificOperator + * + * This interface represents an operator that is designed to work with exactly + * one entity type. + * + * In almost all of the cases it is not necessary to have this limitation, + * because the action is not connected to the event. This mechanism suits + * special cases. + * + * @package OCP\WorkflowEngine + * @since 18.0.0 + */ +interface ISpecificOperator extends IOperator { + + /** + * returns the id of the entity the operator is designed for + * + * Example: 'WorkflowEngine_Entity_File' + * + * @since 18.0.0 + */ + public function getEntityId():string; +} From 4c2fdbb9085514692bb86a73bb415a41ccd209f4 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 27 Aug 2019 16:52:00 +0200 Subject: [PATCH 10/68] merge IOperator with IOperation for simplicity Signed-off-by: Arthur Schiwon --- apps/workflowengine/lib/Manager.php | 15 ++- .../workflowengine/lib/Settings/ASettings.php | 16 ++-- lib/composer/composer/autoload_classmap.php | 5 +- lib/composer/composer/autoload_static.php | 5 +- ...plexOperator.php => IComplexOperation.php} | 4 +- lib/public/WorkflowEngine/IManager.php | 5 +- lib/public/WorkflowEngine/IOperation.php | 54 ++++++++++- lib/public/WorkflowEngine/IOperator.php | 91 ------------------- ...ficOperator.php => ISpecificOperation.php} | 4 +- 9 files changed, 77 insertions(+), 122 deletions(-) rename lib/public/WorkflowEngine/{IComplexOperator.php => IComplexOperation.php} (94%) delete mode 100644 lib/public/WorkflowEngine/IOperator.php rename lib/public/WorkflowEngine/{ISpecificOperator.php => ISpecificOperation.php} (94%) diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index d19aa31547a..0a1b7fab6c5 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -40,7 +40,6 @@ use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IEntityAware; use OCP\WorkflowEngine\IManager; use OCP\WorkflowEngine\IOperation; -use OCP\WorkflowEngine\IOperator; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -76,7 +75,7 @@ class Manager implements IManager, IEntityAware { /** @var IEntity[] */ protected $registeredEntities = []; - /** @var IOperator[] */ + /** @var IOperation[] */ protected $registeredOperators = []; /** @var ILogger */ @@ -550,16 +549,16 @@ class Manager implements IManager, IEntityAware { * @return IEntity[] */ public function getEntitiesList(): array { - $this->eventDispatcher->dispatch('OCP\WorkflowEngine::registerEntities', new GenericEvent($this)); + $this->eventDispatcher->dispatch(IManager::EVENT_NAME_REG_ENTITY, new GenericEvent($this)); return array_merge($this->getBuildInEntities(), $this->registeredEntities); } /** - * @return IOperator[] + * @return IOperation[] */ public function getOperatorList(): array { - $this->eventDispatcher->dispatch('OCP\WorkflowEngine::registerOperators', new GenericEvent($this)); + $this->eventDispatcher->dispatch(IManager::EVENT_NAME_REG_OPERATION, new GenericEvent($this)); return array_merge($this->getBuildInOperators(), $this->registeredOperators); } @@ -574,8 +573,8 @@ class Manager implements IManager, IEntityAware { $this->registeredEntities[$entity->getId()] = $entity; } - public function registerOperator(IOperator $operator): void { - $this->registeredOperators[$operator->getId()] = $operator; + public function registerOperation(IOperation $operator): void { + $this->registeredOperators[get_class($operator)] = $operator; } /** @@ -593,7 +592,7 @@ class Manager implements IManager, IEntityAware { } /** - * @return IOperator[] + * @return IOperation[] */ protected function getBuildInOperators(): array { try { diff --git a/apps/workflowengine/lib/Settings/ASettings.php b/apps/workflowengine/lib/Settings/ASettings.php index 78a23d924c7..af3208fce27 100644 --- a/apps/workflowengine/lib/Settings/ASettings.php +++ b/apps/workflowengine/lib/Settings/ASettings.php @@ -30,11 +30,11 @@ use OCP\AppFramework\Http\TemplateResponse; use OCP\IInitialStateService; use OCP\IL10N; use OCP\Settings\ISettings; -use OCP\WorkflowEngine\IComplexOperator; +use OCP\WorkflowEngine\IComplexOperation; use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IEntityEvent; -use OCP\WorkflowEngine\IOperator; -use OCP\WorkflowEngine\ISpecificOperator; +use OCP\WorkflowEngine\IOperation; +use OCP\WorkflowEngine\ISpecificOperation; use Symfony\Component\EventDispatcher\EventDispatcherInterface; abstract class ASettings implements ISettings { @@ -140,18 +140,18 @@ abstract class ASettings implements ISettings { } private function operatorsToArray(array $operators) { - $operators = array_filter($operators, function(IOperator $operator) { + $operators = array_filter($operators, function(IOperation $operator) { return $operator->isAvailableForScope($this->getScope()); }); - return array_map(function (IOperator $operator) { + return array_map(function (IOperation $operator) { return [ - 'id' => $operator->getId(), + 'id' => get_class($operator), 'icon' => $operator->getIcon(), 'name' => $operator->getDisplayName(), 'description' => $operator->getDescription(), - 'fixedEntity' => $operator instanceof ISpecificOperator ? $operator->getEntityId() : '', - 'isComplex' => $operator instanceof IComplexOperator, + 'fixedEntity' => $operator instanceof ISpecificOperation ? $operator->getEntityId() : '', + 'isComplex' => $operator instanceof IComplexOperation, ]; }, $operators); } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 76322993b48..f8fb8b76d6b 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -439,14 +439,13 @@ return array( 'OCP\\User\\Backend\\ISetPasswordBackend' => $baseDir . '/lib/public/User/Backend/ISetPasswordBackend.php', 'OCP\\Util' => $baseDir . '/lib/public/Util.php', 'OCP\\WorkflowEngine\\ICheck' => $baseDir . '/lib/public/WorkflowEngine/ICheck.php', - 'OCP\\WorkflowEngine\\IComplexOperator' => $baseDir . '/lib/public/WorkflowEngine/IComplexOperator.php', + 'OCP\\WorkflowEngine\\IComplexOperation' => $baseDir . '/lib/public/WorkflowEngine/IComplexOperation.php', 'OCP\\WorkflowEngine\\IEntity' => $baseDir . '/lib/public/WorkflowEngine/IEntity.php', 'OCP\\WorkflowEngine\\IEntityAware' => $baseDir . '/lib/public/WorkflowEngine/IEntityAware.php', 'OCP\\WorkflowEngine\\IEntityEvent' => $baseDir . '/lib/public/WorkflowEngine/IEntityEvent.php', 'OCP\\WorkflowEngine\\IManager' => $baseDir . '/lib/public/WorkflowEngine/IManager.php', 'OCP\\WorkflowEngine\\IOperation' => $baseDir . '/lib/public/WorkflowEngine/IOperation.php', - 'OCP\\WorkflowEngine\\IOperator' => $baseDir . '/lib/public/WorkflowEngine/IOperator.php', - 'OCP\\WorkflowEngine\\ISpecificOperator' => $baseDir . '/lib/public/WorkflowEngine/ISpecificOperator.php', + 'OCP\\WorkflowEngine\\ISpecificOperation' => $baseDir . '/lib/public/WorkflowEngine/ISpecificOperation.php', 'OC\\Accounts\\Account' => $baseDir . '/lib/private/Accounts/Account.php', 'OC\\Accounts\\AccountManager' => $baseDir . '/lib/private/Accounts/AccountManager.php', 'OC\\Accounts\\AccountProperty' => $baseDir . '/lib/private/Accounts/AccountProperty.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index f164d0b15bd..ab47f44ff8e 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -473,14 +473,13 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\User\\Backend\\ISetPasswordBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISetPasswordBackend.php', 'OCP\\Util' => __DIR__ . '/../../..' . '/lib/public/Util.php', 'OCP\\WorkflowEngine\\ICheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ICheck.php', - 'OCP\\WorkflowEngine\\IComplexOperator' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IComplexOperator.php', + 'OCP\\WorkflowEngine\\IComplexOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IComplexOperation.php', 'OCP\\WorkflowEngine\\IEntity' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntity.php', 'OCP\\WorkflowEngine\\IEntityAware' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityAware.php', 'OCP\\WorkflowEngine\\IEntityEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityEvent.php', 'OCP\\WorkflowEngine\\IManager' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IManager.php', 'OCP\\WorkflowEngine\\IOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IOperation.php', - 'OCP\\WorkflowEngine\\IOperator' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IOperator.php', - 'OCP\\WorkflowEngine\\ISpecificOperator' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ISpecificOperator.php', + 'OCP\\WorkflowEngine\\ISpecificOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ISpecificOperation.php', 'OC\\Accounts\\Account' => __DIR__ . '/../../..' . '/lib/private/Accounts/Account.php', 'OC\\Accounts\\AccountManager' => __DIR__ . '/../../..' . '/lib/private/Accounts/AccountManager.php', 'OC\\Accounts\\AccountProperty' => __DIR__ . '/../../..' . '/lib/private/Accounts/AccountProperty.php', diff --git a/lib/public/WorkflowEngine/IComplexOperator.php b/lib/public/WorkflowEngine/IComplexOperation.php similarity index 94% rename from lib/public/WorkflowEngine/IComplexOperator.php rename to lib/public/WorkflowEngine/IComplexOperation.php index ee2d3d0a183..f3ba6d014a4 100644 --- a/lib/public/WorkflowEngine/IComplexOperator.php +++ b/lib/public/WorkflowEngine/IComplexOperation.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace OCP\WorkflowEngine; /** - * Interface IComplexOperator + * Interface IComplexOperation * * This interface represents an operator that is less generic and indicates * that some of the tasks it does itself instead of relying on the engine. @@ -40,4 +40,4 @@ namespace OCP\WorkflowEngine; * * @sincee 18.0.0 */ -interface IComplexOperator extends IOperator { } +interface IComplexOperation extends IOperation { } diff --git a/lib/public/WorkflowEngine/IManager.php b/lib/public/WorkflowEngine/IManager.php index 6d4bacc8e17..8ef7a3a03e8 100644 --- a/lib/public/WorkflowEngine/IManager.php +++ b/lib/public/WorkflowEngine/IManager.php @@ -37,6 +37,9 @@ interface IManager { const SCOPE_ADMIN = 0; const SCOPE_USER = 1; + const EVENT_NAME_REG_OPERATION = 'OCP\WorkflowEngine::registerOperations'; + const EVENT_NAME_REG_ENTITY = 'OCP\WorkflowEngine::registerEntities'; + /** * @param IStorage $storage * @param string $path @@ -66,5 +69,5 @@ interface IManager { * * @since 18.0.0 */ - public function registerOperator(IOperator $operator): void; + public function registerOperation(IOperation $operator): void; } diff --git a/lib/public/WorkflowEngine/IOperation.php b/lib/public/WorkflowEngine/IOperation.php index 491a805909c..0862588e86f 100644 --- a/lib/public/WorkflowEngine/IOperation.php +++ b/lib/public/WorkflowEngine/IOperation.php @@ -31,11 +31,57 @@ namespace OCP\WorkflowEngine; */ interface IOperation { /** - * @param string $name - * @param array[] $checks - * @param string $operation + * returns a translated name to be presented in the web interface + * + * Example: "Automated tagging" (en), "Aŭtomata etikedado" (eo) + * + * @since 18.0.0 + */ + public function getDisplayName(): string; + + /** + * returns a translated, descriptive text to be presented in the web interface. + * + * It should be short and precise. + * + * Example: "Tag based automatic deletion of files after a given time." (en) + * + * @since 18.0.0 + */ + public function getDescription(): string; + + /** + * returns the URL to the icon of the operator for display in the web interface. + * + * Usually, the implementation would utilize the `imagePath()` method of the + * `\OCP\IURLGenerator` instance and simply return its result. + * + * Example implementation: return $this->urlGenerator->imagePath('myApp', 'cat.svg'); + * + * @since 18.0.0 + */ + public function getIcon(): string; + + /** + * returns whether the operation can be used in the requested scope. + * + * Scope IDs are defined as constants in OCP\WorkflowEngine\IManager. At + * time of writing these are SCOPE_ADMIN and SCOPE_USER. + * + * For possibly unknown future scopes the recommended behaviour is: if + * user scope is permitted, the default behaviour should return `true`, + * otherwise `false`. + * + * @since 18.0.0 + */ + public function isAvailableForScope(int $scope): bool; + + /** + * Validates whether a configured workflow rule is valid. If it is not, + * an `\UnexpectedValueException` is supposed to be thrown. + * * @throws \UnexpectedValueException * @since 9.1 */ - public function validateOperation($name, array $checks, $operation); + public function validateOperation(string $name, array $checks, string $operation): void; } diff --git a/lib/public/WorkflowEngine/IOperator.php b/lib/public/WorkflowEngine/IOperator.php deleted file mode 100644 index 70c80d98c45..00000000000 --- a/lib/public/WorkflowEngine/IOperator.php +++ /dev/null @@ -1,91 +0,0 @@ - - * - * @author Arthur Schiwon - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -namespace OCP\WorkflowEngine; - -/** - * @since 18.0.0 - */ -interface IOperator { - /** - * returns the unique identity of the operator - * - * It is recommended to use the namespaced class name of the IOperator - * implementation. Especially workflow applications released before - * Nextcloud 18 should chose this as id for compatibility. - * - * Example: OCA\FilesAutomatedTagging\Operation - * - * @since 18.0.0 - */ - public function getId(): string; - - /** - * returns a translated name to be presented in the web interface - * - * Example: "Automated tagging" (en), "Aŭtomata etikedado" (eo) - * - * @since 18.0.0 - */ - public function getDisplayName(): string; - - /** - * returns a translated, descriptive text to be presented in the web interface. - * - * It should be short and precise. - * - * Example: "Tag based automatic deletion of files after a given time." (en) - * - * @since 18.0.0 - */ - public function getDescription(): string; - - /** - * returns the URL to the icon of the operator for display in the web interface. - * - * Usually, the implementation would utilize the `imagePath()` method of the - * `\OCP\IURLGenerator` instance and simply return its result. - * - * Example implementation: return $this->urlGenerator->imagePath('myApp', 'cat.svg'); - * - * @since 18.0.0 - */ - public function getIcon(): string; - - /** - * returns whether the operation can be used in the requested scope. - * - * Scope IDs are defined as constants in OCP\WorkflowEngine\IManager. At - * time of writing these are SCOPE_ADMIN and SCOPE_USER. - * - * For possibly unknown future scopes the recommended behaviour is: if - * user scope is permitted, the default behaviour should return `true`, - * otherwise `false`. - * - * @since 18.0.0 - */ - public function isAvailableForScope(int $scope): bool; - - -} diff --git a/lib/public/WorkflowEngine/ISpecificOperator.php b/lib/public/WorkflowEngine/ISpecificOperation.php similarity index 94% rename from lib/public/WorkflowEngine/ISpecificOperator.php rename to lib/public/WorkflowEngine/ISpecificOperation.php index a5ae9fc1841..0b26770a13a 100644 --- a/lib/public/WorkflowEngine/ISpecificOperator.php +++ b/lib/public/WorkflowEngine/ISpecificOperation.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace OCP\WorkflowEngine; /** - * Interface ISpecificOperator + * Interface ISpecificOperation * * This interface represents an operator that is designed to work with exactly * one entity type. @@ -37,7 +37,7 @@ namespace OCP\WorkflowEngine; * @package OCP\WorkflowEngine * @since 18.0.0 */ -interface ISpecificOperator extends IOperator { +interface ISpecificOperation extends IOperation { /** * returns the id of the entity the operator is designed for From 26b19b73a46f714031237aa838c0011541f2a8c9 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 27 Aug 2019 17:40:00 +0200 Subject: [PATCH 11/68] remove IEntity's getId in favor of class name Signed-off-by: Arthur Schiwon --- apps/workflowengine/lib/Entity/File.php | 4 ---- apps/workflowengine/lib/Manager.php | 2 +- apps/workflowengine/lib/Settings/ASettings.php | 2 +- lib/public/WorkflowEngine/IEntity.php | 14 -------------- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/apps/workflowengine/lib/Entity/File.php b/apps/workflowengine/lib/Entity/File.php index 0ba3502450a..d4f41625e4e 100644 --- a/apps/workflowengine/lib/Entity/File.php +++ b/apps/workflowengine/lib/Entity/File.php @@ -41,10 +41,6 @@ class File implements IEntity { $this->urlGenerator = $urlGenerator; } - public function getId(): string { - return 'WorkflowEngine_Entity_File'; - } - public function getName(): string { return $this->l10n->t('File'); } diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index 0a1b7fab6c5..adde5309195 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -570,7 +570,7 @@ class Manager implements IManager, IEntityAware { * @since 18.0.0 */ public function registerEntity(IEntity $entity): void { - $this->registeredEntities[$entity->getId()] = $entity; + $this->registeredEntities[get_class($entity)] = $entity; } public function registerOperation(IOperation $operator): void { diff --git a/apps/workflowengine/lib/Settings/ASettings.php b/apps/workflowengine/lib/Settings/ASettings.php index af3208fce27..7423c51fd93 100644 --- a/apps/workflowengine/lib/Settings/ASettings.php +++ b/apps/workflowengine/lib/Settings/ASettings.php @@ -131,7 +131,7 @@ abstract class ASettings implements ISettings { }, $entity->getEvents()); return [ - 'id' => $entity->getId(), + 'id' => get_class($entity), 'icon' => $entity->getIcon(), 'name' => $entity->getName(), 'events' => $events, diff --git a/lib/public/WorkflowEngine/IEntity.php b/lib/public/WorkflowEngine/IEntity.php index 2e77b741367..5ac1082a5a3 100644 --- a/lib/public/WorkflowEngine/IEntity.php +++ b/lib/public/WorkflowEngine/IEntity.php @@ -38,20 +38,6 @@ namespace OCP\WorkflowEngine; */ interface IEntity { - /** - * returns a unique ID of the entity. - * - * It can be, but does not need to be the class name of the entitiy. Beware - * that it will be referenced in the database when rules are established, - * so it should not change over the course of the app life. - * - * Example 1: "OCA/MyApp/Entity/Cat" - * Example 2: "myapp_Cats" - * - * @since 18.0.0 - */ - public function getId(): string; - /** * returns a translated name to be presented in the web interface. * From fe2a78609ab2b69e5540657ffea50d3c1f859001 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 27 Aug 2019 17:47:44 +0200 Subject: [PATCH 12/68] add missing dep Signed-off-by: Arthur Schiwon --- apps/workflowengine/tests/ManagerTest.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/workflowengine/tests/ManagerTest.php b/apps/workflowengine/tests/ManagerTest.php index b25adb96f15..a497cd85a8c 100644 --- a/apps/workflowengine/tests/ManagerTest.php +++ b/apps/workflowengine/tests/ManagerTest.php @@ -29,6 +29,7 @@ use OCP\IDBConnection; use OCP\IL10N; use OCP\ILogger; use OCP\IServerContainer; +use OCP\IUserSession; use OCP\WorkflowEngine\ICheck; use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IManager; @@ -55,6 +56,8 @@ class ManagerTest extends TestCase { protected $eventDispatcher; /** @var MockObject|IServerContainer */ protected $container; + /** @var MockObject|IUserSession */ + protected $session; protected function setUp() { parent::setUp(); @@ -70,13 +73,15 @@ class ManagerTest extends TestCase { $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); $this->logger = $this->createMock(ILogger::class); + $this->session = $this->createMock(IUserSession::class); $this->manager = new Manager( \OC::$server->getDatabaseConnection(), $this->container, $l, $this->eventDispatcher, - $this->logger + $this->logger, + $this->session ); $this->clearTables(); } From 827dd896fa903bb86f3434034952bb0e435a230c Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 27 Aug 2019 17:51:10 +0200 Subject: [PATCH 13/68] extend DB table, manager, controller with support for entity events Signed-off-by: Arthur Schiwon --- .../lib/Controller/AWorkflowController.php | 20 ++++-- apps/workflowengine/lib/Manager.php | 63 ++++++++++++++++--- .../Version2019Date20190808074233.php | 14 +++++ apps/workflowengine/tests/ManagerTest.php | 7 ++- 4 files changed, 90 insertions(+), 14 deletions(-) diff --git a/apps/workflowengine/lib/Controller/AWorkflowController.php b/apps/workflowengine/lib/Controller/AWorkflowController.php index 2e54e417a34..24f57321834 100644 --- a/apps/workflowengine/lib/Controller/AWorkflowController.php +++ b/apps/workflowengine/lib/Controller/AWorkflowController.php @@ -94,10 +94,16 @@ abstract class AWorkflowController extends OCSController { * @throws OCSForbiddenException * @throws OCSException */ - public function create(string $class, string $name, array $checks, string $operation): DataResponse { + public function create( + string $class, + string $name, + array $checks, + string $operation, + array $events + ): DataResponse { $context = $this->getScopeContext(); try { - $operation = $this->manager->addOperation($class, $name, $checks, $operation, $context); + $operation = $this->manager->addOperation($class, $name, $checks, $operation, $context, $events); $operation = $this->manager->formatOperation($operation); return new DataResponse($operation); } catch (\UnexpectedValueException $e) { @@ -114,9 +120,15 @@ abstract class AWorkflowController extends OCSController { * @throws OCSForbiddenException * @throws OCSException */ - public function update(int $id, string $name, array $checks, string $operation): DataResponse { + public function update( + int $id, + string $name, + array $checks, + string $operation, + array $events + ): DataResponse { try { - $operation = $this->manager->updateOperation($id, $name, $checks, $operation, $this->getScopeContext()); + $operation = $this->manager->updateOperation($id, $name, $checks, $operation, $this->getScopeContext(), $events); $operation = $this->manager->formatOperation($operation); return new DataResponse($operation); } catch (\UnexpectedValueException $e) { diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index adde5309195..d0376e703e0 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -38,6 +38,7 @@ use OCP\IUserSession; use OCP\WorkflowEngine\ICheck; use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IEntityAware; +use OCP\WorkflowEngine\IEntityEvent; use OCP\WorkflowEngine\IManager; use OCP\WorkflowEngine\IOperation; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -238,7 +239,13 @@ class Manager implements IManager, IEntityAware { throw new \UnexpectedValueException($this->l->t('Operation #%s does not exist', [$id])); } - protected function insertOperation(string $class, string $name, array $checkIds, string $operation): int { + protected function insertOperation( + string $class, + string $name, + array $checkIds, + string $operation, + array $events + ): int { $query = $this->connection->getQueryBuilder(); $query->insert('flow_operations') ->values([ @@ -246,6 +253,7 @@ class Manager implements IManager, IEntityAware { 'name' => $query->createNamedParameter($name), 'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))), 'operation' => $query->createNamedParameter($operation), + 'events' => $query->createNamedParameter(json_encode($events)) ]); $query->execute(); @@ -261,8 +269,15 @@ class Manager implements IManager, IEntityAware { * @throws \UnexpectedValueException * @throws DBALException */ - public function addOperation($class, $name, array $checks, $operation, ScopeContext $scope) { - $this->validateOperation($class, $name, $checks, $operation); + public function addOperation( + string $class, + string $name, + array $checks, + string $operation, + ScopeContext $scope, + array $events + ) { + $this->validateOperation($class, $name, $checks, $operation, $events); $this->connection->beginTransaction(); @@ -272,7 +287,7 @@ class Manager implements IManager, IEntityAware { $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']); } - $id = $this->insertOperation($class, $name, $checkIds, $operation); + $id = $this->insertOperation($class, $name, $checkIds, $operation, $events); $this->addScope($id, $scope); $this->connection->commit(); @@ -321,12 +336,19 @@ class Manager implements IManager, IEntityAware { * @throws \DomainException * @throws DBALException */ - public function updateOperation($id, $name, array $checks, $operation, ScopeContext $scopeContext): array { + public function updateOperation( + int $id, + string $name, + array $checks, + string $operation, + ScopeContext $scopeContext, + array $events + ): 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); + $this->validateOperation($row['class'], $name, $checks, $operation, $events); $checkIds = []; try { @@ -340,6 +362,7 @@ class Manager implements IManager, IEntityAware { ->set('name', $query->createNamedParameter($name)) ->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds)))) ->set('operation', $query->createNamedParameter($operation)) + ->set('events', $query->createNamedParameter(json_encode($events))) ->where($query->expr()->eq('id', $query->createNamedParameter($id))); $query->execute(); $this->connection->commit(); @@ -388,6 +411,30 @@ class Manager implements IManager, IEntityAware { return $result; } + protected function validateEvents($events) { + foreach ($events as $entity => $eventNames) { + try { + /** @var IEntity $instance */ + $instance = $this->container->query($entity); + } catch (QueryException $e) { + throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity])); + } + + if(!$instance instanceof IEntity) { + throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity])); + } + + $availableEvents = array_reduce($instance->getEvents(), function(array $carry, IEntityEvent $event) { + $carry[] = $event->getEventName(); + }, []); + foreach($eventNames as $event) { + if(!in_array($event, $availableEvents, true)) { + throw new \UnexpectedValueException($this->l->t('Entity %s has no event %s', [$entity, $event])); + } + } + } + } + /** * @param string $class * @param string $name @@ -395,7 +442,7 @@ class Manager implements IManager, IEntityAware { * @param string $operation * @throws \UnexpectedValueException */ - protected function validateOperation($class, $name, array $checks, $operation) { + protected function validateOperation($class, $name, array $checks, $operation, array $events) { try { /** @var IOperation $instance */ $instance = $this->container->query($class); @@ -407,6 +454,8 @@ class Manager implements IManager, IEntityAware { throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class])); } + $this->validateEvents($events); + $instance->validateOperation($name, $checks, $operation); foreach ($checks as $check) { diff --git a/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php b/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php index cedee43a9eb..2b9a8aa17cd 100644 --- a/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php +++ b/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace OCA\WorkflowEngine\Migration; use Closure; +use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Types\Type; use OCP\DB\ISchemaWrapper; use OCP\Migration\SimpleMigrationStep; @@ -69,7 +70,13 @@ class Version2019Date20190808074233 extends SimpleMigrationStep { $table->addColumn('operation', Type::TEXT, [ 'notnull' => false, ]); + $this->addEventsColumn($table); $table->setPrimaryKey(['id']); + } else { + $table = $schema->getTable('flow_operations'); + if(!$table->hasColumn('events')) { + $this->addEventsColumn($table); + } } if (!$schema->hasTable('flow_operations_scope')) { @@ -97,4 +104,11 @@ class Version2019Date20190808074233 extends SimpleMigrationStep { return $schema; } + + protected function addEventsColumn(Table $table) { + $table->addColumn('events', Type::TEXT, [ + 'notnull' => true, + 'default' => '[]', + ]); + } } diff --git a/apps/workflowengine/tests/ManagerTest.php b/apps/workflowengine/tests/ManagerTest.php index a497cd85a8c..a2d30944f78 100644 --- a/apps/workflowengine/tests/ManagerTest.php +++ b/apps/workflowengine/tests/ManagerTest.php @@ -290,19 +290,19 @@ class ManagerTest extends TestCase { $check2 = ['class' => 'OCA\WFE\C33', 'operator' => 'eq', 'value' => 23456]; /** @noinspection PhpUnhandledExceptionInspection */ - $op = $this->manager->updateOperation($opId1, 'Test01a', [$check1, $check2], 'foohur', $adminScope); + $op = $this->manager->updateOperation($opId1, 'Test01a', [$check1, $check2], 'foohur', $adminScope, []); $this->assertSame('Test01a', $op['name']); $this->assertSame('foohur', $op['operation']); /** @noinspection PhpUnhandledExceptionInspection */ - $op = $this->manager->updateOperation($opId2, 'Test02a', [$check1], 'barfoo', $userScope); + $op = $this->manager->updateOperation($opId2, 'Test02a', [$check1], 'barfoo', $userScope, []); $this->assertSame('Test02a', $op['name']); $this->assertSame('barfoo', $op['operation']); foreach([[$adminScope, $opId2], [$userScope, $opId1]] as $run) { try { /** @noinspection PhpUnhandledExceptionInspection */ - $this->manager->updateOperation($run[1], 'Evil', [$check2], 'hackx0r', $run[0]); + $this->manager->updateOperation($run[1], 'Evil', [$check2], 'hackx0r', $run[0], []); $this->assertTrue(false, 'DomainException not thrown'); } catch (\DomainException $e) { $this->assertTrue(true); @@ -398,4 +398,5 @@ class ManagerTest extends TestCase { $this->assertSame(1, $entityTypeCounts[0]); $this->assertSame(1, $entityTypeCounts[1]); } + } From ed58343e60e8fed3fe0104e3f334a116a528959f Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 28 Aug 2019 15:00:02 +0200 Subject: [PATCH 14/68] split events DB field into entity and events, adjust biz logic Signed-off-by: Arthur Schiwon --- .../lib/Controller/AWorkflowController.php | 7 ++- apps/workflowengine/lib/Manager.php | 59 +++++++++++-------- .../PopulateNewlyIntroducedDatabaseFields.php | 13 ++++ .../Version2019Date20190808074233.php | 12 ++-- apps/workflowengine/tests/ManagerTest.php | 48 +++++++++------ 5 files changed, 92 insertions(+), 47 deletions(-) diff --git a/apps/workflowengine/lib/Controller/AWorkflowController.php b/apps/workflowengine/lib/Controller/AWorkflowController.php index 24f57321834..2e3186e380d 100644 --- a/apps/workflowengine/lib/Controller/AWorkflowController.php +++ b/apps/workflowengine/lib/Controller/AWorkflowController.php @@ -99,11 +99,12 @@ abstract class AWorkflowController extends OCSController { string $name, array $checks, string $operation, + string $entity, array $events ): DataResponse { $context = $this->getScopeContext(); try { - $operation = $this->manager->addOperation($class, $name, $checks, $operation, $context, $events); + $operation = $this->manager->addOperation($class, $name, $checks, $operation, $context, $entity, $events); $operation = $this->manager->formatOperation($operation); return new DataResponse($operation); } catch (\UnexpectedValueException $e) { @@ -125,10 +126,12 @@ abstract class AWorkflowController extends OCSController { string $name, array $checks, string $operation, + string $entity, array $events ): DataResponse { try { - $operation = $this->manager->updateOperation($id, $name, $checks, $operation, $this->getScopeContext(), $events); + $context = $this->getScopeContext(); + $operation = $this->manager->updateOperation($id, $name, $checks, $operation, $context, $entity, $events); $operation = $this->manager->formatOperation($operation); return new DataResponse($operation); } catch (\UnexpectedValueException $e) { diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index d0376e703e0..f19aa46d4ab 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -36,6 +36,7 @@ use OCP\ILogger; use OCP\IServerContainer; use OCP\IUserSession; use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IComplexOperation; use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IEntityAware; use OCP\WorkflowEngine\IEntityEvent; @@ -244,6 +245,7 @@ class Manager implements IManager, IEntityAware { string $name, array $checkIds, string $operation, + string $entity, array $events ): int { $query = $this->connection->getQueryBuilder(); @@ -253,6 +255,7 @@ class Manager implements IManager, IEntityAware { 'name' => $query->createNamedParameter($name), 'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))), 'operation' => $query->createNamedParameter($operation), + 'entity' => $query->createNamedParameter($entity), 'events' => $query->createNamedParameter(json_encode($events)) ]); $query->execute(); @@ -275,9 +278,10 @@ class Manager implements IManager, IEntityAware { array $checks, string $operation, ScopeContext $scope, + string $entity, array $events ) { - $this->validateOperation($class, $name, $checks, $operation, $events); + $this->validateOperation($class, $name, $checks, $operation, $entity, $events); $this->connection->beginTransaction(); @@ -287,7 +291,7 @@ class Manager implements IManager, IEntityAware { $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']); } - $id = $this->insertOperation($class, $name, $checkIds, $operation, $events); + $id = $this->insertOperation($class, $name, $checkIds, $operation, $entity, $events); $this->addScope($id, $scope); $this->connection->commit(); @@ -342,13 +346,14 @@ class Manager implements IManager, IEntityAware { array $checks, string $operation, ScopeContext $scopeContext, + string $entity, array $events ): 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, $events); + $this->validateOperation($row['class'], $name, $checks, $operation, $entity, $events); $checkIds = []; try { @@ -362,6 +367,7 @@ class Manager implements IManager, IEntityAware { ->set('name', $query->createNamedParameter($name)) ->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds)))) ->set('operation', $query->createNamedParameter($operation)) + ->set('entity', $query->createNamedParameter($entity)) ->set('events', $query->createNamedParameter(json_encode($events))) ->where($query->expr()->eq('id', $query->createNamedParameter($id))); $query->execute(); @@ -411,27 +417,34 @@ class Manager implements IManager, IEntityAware { return $result; } - protected function validateEvents($events) { - foreach ($events as $entity => $eventNames) { - try { - /** @var IEntity $instance */ - $instance = $this->container->query($entity); - } catch (QueryException $e) { - throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity])); - } + protected function validateEvents(string $entity, array $events, IOperation $operation) { + try { + /** @var IEntity $instance */ + $instance = $this->container->query($entity); + } catch (QueryException $e) { + throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity])); + } - if(!$instance instanceof IEntity) { - throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity])); - } + if(!$instance instanceof IEntity) { + throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity])); + } - $availableEvents = array_reduce($instance->getEvents(), function(array $carry, IEntityEvent $event) { - $carry[] = $event->getEventName(); - }, []); - foreach($eventNames as $event) { - if(!in_array($event, $availableEvents, true)) { - throw new \UnexpectedValueException($this->l->t('Entity %s has no event %s', [$entity, $event])); - } + if(empty($events)) { + if(!$operation instanceof IComplexOperation) { + throw new \UnexpectedValueException($this->l->t('No events are chosen.')); } + return; + } + + $availableEvents = []; + foreach ($instance->getEvents() as $event) { + /** @var IEntityEvent $event */ + $availableEvents[] = $event->getEventName(); + } + + $diff = array_diff($events, $availableEvents); + if(!empty($diff)) { + throw new \UnexpectedValueException($this->l->t('Entity %s has no event %s', [$entity, array_shift($diff)])); } } @@ -442,7 +455,7 @@ class Manager implements IManager, IEntityAware { * @param string $operation * @throws \UnexpectedValueException */ - protected function validateOperation($class, $name, array $checks, $operation, array $events) { + protected function validateOperation($class, $name, array $checks, $operation, string $entity, array $events) { try { /** @var IOperation $instance */ $instance = $this->container->query($class); @@ -454,7 +467,7 @@ class Manager implements IManager, IEntityAware { throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class])); } - $this->validateEvents($events); + $this->validateEvents($entity, $events, $instance); $instance->validateOperation($name, $checks, $operation); diff --git a/apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php b/apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php index 43595d1c7cf..8b3f3d5a9c3 100644 --- a/apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php +++ b/apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php @@ -25,6 +25,7 @@ declare(strict_types=1); namespace OCA\WorkflowEngine\Migration; use Doctrine\DBAL\Driver\Statement; +use OCA\WorkflowEngine\Entity\File; use OCP\IDBConnection; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; @@ -49,6 +50,18 @@ class PopulateNewlyIntroducedDatabaseFields implements IRepairStep { $this->populateScopeTable($result); $result->closeCursor(); + + $this->populateEntityCol(); + } + + protected function populateEntityCol() { + $qb = $this->dbc->getQueryBuilder(); + + $qb->update('flow_operations') + ->set('entity', File::class) + ->where($qb->expr()->emptyString('entity')) + ->execute(); + } protected function populateScopeTable(Statement $ids): void { diff --git a/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php b/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php index 2b9a8aa17cd..ffea3c88d01 100644 --- a/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php +++ b/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php @@ -70,12 +70,12 @@ class Version2019Date20190808074233 extends SimpleMigrationStep { $table->addColumn('operation', Type::TEXT, [ 'notnull' => false, ]); - $this->addEventsColumn($table); + $this->addEntityColumns($table); $table->setPrimaryKey(['id']); } else { $table = $schema->getTable('flow_operations'); - if(!$table->hasColumn('events')) { - $this->addEventsColumn($table); + if(!$table->hasColumn('entity')) { + $this->addEntityColumns($table); } } @@ -105,7 +105,11 @@ class Version2019Date20190808074233 extends SimpleMigrationStep { return $schema; } - protected function addEventsColumn(Table $table) { + protected function addEntityColumns(Table $table) { + $table->addColumn('entity', Type::STRING, [ + 'notnull' => true, + 'length' => 256, + ]); $table->addColumn('events', Type::TEXT, [ 'notnull' => true, 'default' => '[]', diff --git a/apps/workflowengine/tests/ManagerTest.php b/apps/workflowengine/tests/ManagerTest.php index a2d30944f78..3a0cd18a946 100644 --- a/apps/workflowengine/tests/ManagerTest.php +++ b/apps/workflowengine/tests/ManagerTest.php @@ -22,6 +22,7 @@ namespace OCA\WorkflowEngine\Tests; +use OC\L10N\L10N; use OCA\WorkflowEngine\Entity\File; use OCA\WorkflowEngine\Helper\ScopeContext; use OCA\WorkflowEngine\Manager; @@ -29,6 +30,7 @@ use OCP\IDBConnection; use OCP\IL10N; use OCP\ILogger; use OCP\IServerContainer; +use OCP\IURLGenerator; use OCP\IUserSession; use OCP\WorkflowEngine\ICheck; use OCP\WorkflowEngine\IEntity; @@ -141,24 +143,25 @@ class ManagerTest extends TestCase { public function testScope() { $adminScope = $this->buildScope(); $userScope = $this->buildScope('jackie'); + $entity = File::class; $opId1 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestOp', 'Test01', [11, 22], 'foo'] + ['OCA\WFE\TestOp', 'Test01', [11, 22], 'foo', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]); $opId2 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestOp', 'Test02', [33, 22], 'bar'] + ['OCA\WFE\TestOp', 'Test02', [33, 22], 'bar', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]); $opId3 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestOp', 'Test03', [11, 44], 'foobar'] + ['OCA\WFE\TestOp', 'Test03', [11, 44], 'foobar', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId3, $userScope]); @@ -174,24 +177,25 @@ class ManagerTest extends TestCase { public function testGetAllOperations() { $adminScope = $this->buildScope(); $userScope = $this->buildScope('jackie'); + $entity = File::class; $opId1 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo'] + ['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]); $opId2 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar'] + ['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]); $opId3 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestUserOp', 'Test03', [11, 44], 'foobar'] + ['OCA\WFE\TestUserOp', 'Test03', [11, 44], 'foobar', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId3, $userScope]); @@ -211,36 +215,37 @@ class ManagerTest extends TestCase { public function testGetOperations() { $adminScope = $this->buildScope(); $userScope = $this->buildScope('jackie'); + $entity = File::class; $opId1 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestOp', 'Test01', [11, 22], 'foo'] + ['OCA\WFE\TestOp', 'Test01', [11, 22], 'foo', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]); $opId4 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\OtherTestOp', 'Test04', [5], 'foo'] + ['OCA\WFE\OtherTestOp', 'Test04', [5], 'foo', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId4, $adminScope]); $opId2 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestOp', 'Test02', [33, 22], 'bar'] + ['OCA\WFE\TestOp', 'Test02', [33, 22], 'bar', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]); $opId3 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestOp', 'Test03', [11, 44], 'foobar'] + ['OCA\WFE\TestOp', 'Test03', [11, 44], 'foobar', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId3, $userScope]); $opId5 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\OtherTestOp', 'Test05', [5], 'foobar'] + ['OCA\WFE\OtherTestOp', 'Test05', [5], 'foobar', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId5, $userScope]); @@ -262,12 +267,18 @@ class ManagerTest extends TestCase { public function testUpdateOperation() { $adminScope = $this->buildScope(); $userScope = $this->buildScope('jackie'); + $entity = File::class; $this->container->expects($this->any()) ->method('query') ->willReturnCallback(function ($class) { if(substr($class, -2) === 'Op') { return $this->createMock(IOperation::class); + } else if($class === File::class) { + return $this->getMockBuilder(File::class) + ->setConstructorArgs([$this->createMock(L10N::class), $this->createMock(IURLGenerator::class)]) + ->setMethodsExcept(['getEvents']) + ->getMock(); } return $this->createMock(ICheck::class); }); @@ -275,14 +286,14 @@ class ManagerTest extends TestCase { $opId1 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo'] + ['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]); $opId2 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar'] + ['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]); @@ -290,19 +301,19 @@ class ManagerTest extends TestCase { $check2 = ['class' => 'OCA\WFE\C33', 'operator' => 'eq', 'value' => 23456]; /** @noinspection PhpUnhandledExceptionInspection */ - $op = $this->manager->updateOperation($opId1, 'Test01a', [$check1, $check2], 'foohur', $adminScope, []); + $op = $this->manager->updateOperation($opId1, 'Test01a', [$check1, $check2], 'foohur', $adminScope, $entity, ['postDelete']); $this->assertSame('Test01a', $op['name']); $this->assertSame('foohur', $op['operation']); /** @noinspection PhpUnhandledExceptionInspection */ - $op = $this->manager->updateOperation($opId2, 'Test02a', [$check1], 'barfoo', $userScope, []); + $op = $this->manager->updateOperation($opId2, 'Test02a', [$check1], 'barfoo', $userScope, $entity, ['postDelete']); $this->assertSame('Test02a', $op['name']); $this->assertSame('barfoo', $op['operation']); foreach([[$adminScope, $opId2], [$userScope, $opId1]] as $run) { try { /** @noinspection PhpUnhandledExceptionInspection */ - $this->manager->updateOperation($run[1], 'Evil', [$check2], 'hackx0r', $run[0], []); + $this->manager->updateOperation($run[1], 'Evil', [$check2], 'hackx0r', $run[0], $entity, []); $this->assertTrue(false, 'DomainException not thrown'); } catch (\DomainException $e) { $this->assertTrue(true); @@ -313,18 +324,19 @@ class ManagerTest extends TestCase { public function testDeleteOperation() { $adminScope = $this->buildScope(); $userScope = $this->buildScope('jackie'); + $entity = File::class; $opId1 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo'] + ['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]); $opId2 = $this->invokePrivate( $this->manager, 'insertOperation', - ['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar'] + ['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar', $entity, []] ); $this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]); From 4c717884e3a384d7b97d88aa749ae0130750924d Mon Sep 17 00:00:00 2001 From: blizzz Date: Wed, 28 Aug 2019 22:57:42 +0200 Subject: [PATCH 15/68] fix personal settings class name in xml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Julius Härtl Signed-off-by: blizzz --- apps/workflowengine/appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/workflowengine/appinfo/info.xml b/apps/workflowengine/appinfo/info.xml index e535ffa1432..115994b2a93 100644 --- a/apps/workflowengine/appinfo/info.xml +++ b/apps/workflowengine/appinfo/info.xml @@ -38,7 +38,7 @@ OCA\WorkflowEngine\Settings\Admin OCA\WorkflowEngine\Settings\Section - OCA\WorkflowEngine\Settings\Admin + OCA\WorkflowEngine\Settings\Personal OCA\WorkflowEngine\Settings\Section From 54bdc95cc15853c42eac6e306aadcc325b6e74a2 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 28 Aug 2019 23:56:21 +0200 Subject: [PATCH 16/68] fix missing value and run against empty tables in migration script Signed-off-by: Arthur Schiwon --- .../PopulateNewlyIntroducedDatabaseFields.php | 4 +-- .../Version2019Date20190808074233.php | 28 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php b/apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php index 8b3f3d5a9c3..f715ebc7ef7 100644 --- a/apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php +++ b/apps/workflowengine/lib/Migration/PopulateNewlyIntroducedDatabaseFields.php @@ -58,7 +58,7 @@ class PopulateNewlyIntroducedDatabaseFields implements IRepairStep { $qb = $this->dbc->getQueryBuilder(); $qb->update('flow_operations') - ->set('entity', File::class) + ->set('entity', $qb->createNamedParameter(File::class)) ->where($qb->expr()->emptyString('entity')) ->execute(); @@ -70,8 +70,8 @@ class PopulateNewlyIntroducedDatabaseFields implements IRepairStep { $insertQuery = $qb->insert('flow_operations_scope'); while($id = $ids->fetchColumn(0)) { $insertQuery->values(['operation_id' => $qb->createNamedParameter($id), 'type' => IManager::SCOPE_ADMIN]); + $insertQuery->execute(); } - $insertQuery->execute(); } protected function getIdsWithoutScope(): Statement { diff --git a/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php b/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php index ffea3c88d01..6ac48616b16 100644 --- a/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php +++ b/apps/workflowengine/lib/Migration/Version2019Date20190808074233.php @@ -70,13 +70,11 @@ class Version2019Date20190808074233 extends SimpleMigrationStep { $table->addColumn('operation', Type::TEXT, [ 'notnull' => false, ]); - $this->addEntityColumns($table); + $this->ensureEntityColumns($table); $table->setPrimaryKey(['id']); } else { $table = $schema->getTable('flow_operations'); - if(!$table->hasColumn('entity')) { - $this->addEntityColumns($table); - } + $this->ensureEntityColumns($table); } if (!$schema->hasTable('flow_operations_scope')) { @@ -105,14 +103,18 @@ class Version2019Date20190808074233 extends SimpleMigrationStep { return $schema; } - protected function addEntityColumns(Table $table) { - $table->addColumn('entity', Type::STRING, [ - 'notnull' => true, - 'length' => 256, - ]); - $table->addColumn('events', Type::TEXT, [ - 'notnull' => true, - 'default' => '[]', - ]); + protected function ensureEntityColumns(Table $table) { + if(!$table->hasColumn('entity')) { + $table->addColumn('entity', Type::STRING, [ + 'notnull' => true, + 'length' => 256, + ]); + } + if(!$table->hasColumn('events')) { + $table->addColumn('events', Type::TEXT, [ + 'notnull' => true, + 'default' => '[]', + ]); + } } } From f0f0a7b43ebc4584f5dd6c0473a57eafbaa190d1 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 29 Aug 2019 12:36:35 +0200 Subject: [PATCH 17/68] adds a trigger hint for complex operations Signed-off-by: Arthur Schiwon --- apps/workflowengine/lib/Settings/ASettings.php | 1 + lib/public/WorkflowEngine/IComplexOperation.php | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/workflowengine/lib/Settings/ASettings.php b/apps/workflowengine/lib/Settings/ASettings.php index 7423c51fd93..1781aaa2f55 100644 --- a/apps/workflowengine/lib/Settings/ASettings.php +++ b/apps/workflowengine/lib/Settings/ASettings.php @@ -152,6 +152,7 @@ abstract class ASettings implements ISettings { 'description' => $operator->getDescription(), 'fixedEntity' => $operator instanceof ISpecificOperation ? $operator->getEntityId() : '', 'isComplex' => $operator instanceof IComplexOperation, + 'triggerHint' => $operator instanceof IComplexOperation ? $operator->getTriggerHint() : '', ]; }, $operators); } diff --git a/lib/public/WorkflowEngine/IComplexOperation.php b/lib/public/WorkflowEngine/IComplexOperation.php index f3ba6d014a4..63a4ca2460a 100644 --- a/lib/public/WorkflowEngine/IComplexOperation.php +++ b/lib/public/WorkflowEngine/IComplexOperation.php @@ -38,6 +38,19 @@ namespace OCP\WorkflowEngine; * * @package OCP\WorkflowEngine * - * @sincee 18.0.0 + * @since 18.0.0 */ -interface IComplexOperation extends IOperation { } +interface IComplexOperation extends IOperation { + + /** + * As IComplexOperation chooses the triggering events itself, a hint has + * to be shown to the user so make clear when this operation is becoming + * active. This method returns such a translated string. + * + * Example: "When a file is accessed" (en) + * + * @since 18.0.0 + */ + public function getTriggerHint(): string; + +} From ce4f3598ff4b3787863c024230ea893b7f8a4c86 Mon Sep 17 00:00:00 2001 From: blizzz Date: Thu, 29 Aug 2019 12:41:51 +0200 Subject: [PATCH 18/68] use correct builder instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: blizzz Co-Authored-By: Julius Härtl --- apps/workflowengine/lib/Manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index f19aa46d4ab..7ca24a754dc 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -401,7 +401,7 @@ class Manager implements IManager, IEntityAware { if($result) { $qb = $this->connection->getQueryBuilder(); $result &= (bool)$qb->delete('flow_operations_scope') - ->where($qb->expr()->eq('operation_id', $query->createNamedParameter($id))) + ->where($qb->expr()->eq('operation_id', $qb->createNamedParameter($id))) ->execute(); } $this->connection->commit(); From dcfe4ab2ccd16a084b2e61093ad4c1314b6fd263 Mon Sep 17 00:00:00 2001 From: blizzz Date: Thu, 29 Aug 2019 12:42:37 +0200 Subject: [PATCH 19/68] fix parameter for OCSExcpetion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: blizzz Co-Authored-By: Julius Härtl --- apps/workflowengine/lib/Controller/AWorkflowController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/workflowengine/lib/Controller/AWorkflowController.php b/apps/workflowengine/lib/Controller/AWorkflowController.php index 2e3186e380d..8d51600c7b2 100644 --- a/apps/workflowengine/lib/Controller/AWorkflowController.php +++ b/apps/workflowengine/lib/Controller/AWorkflowController.php @@ -112,7 +112,7 @@ abstract class AWorkflowController extends OCSController { } catch (\DomainException $e) { throw new OCSForbiddenException($e->getMessage(), $e); } catch(DBALException $e) { - throw new OCSException('An internal error occurred', $e); + throw new OCSException('An internal error occurred', $e->getCode(), $e); } } @@ -139,7 +139,7 @@ abstract class AWorkflowController extends OCSController { } catch (\DomainException $e) { throw new OCSForbiddenException($e->getMessage(), $e); } catch(DBALException $e) { - throw new OCSException('An internal error occurred', $e); + throw new OCSException('An internal error occurred', $e->getCode(), $e); } } @@ -157,7 +157,7 @@ abstract class AWorkflowController extends OCSController { } catch (\DomainException $e) { throw new OCSForbiddenException($e->getMessage(), $e); } catch(DBALException $e) { - throw new OCSException('An internal error occurred', $e); + throw new OCSException('An internal error occurred', $e->getCode(), $e); } } } From 0d7f7e5495a56f776e8593e1b6ba80da9e8c0dbd Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 30 Aug 2019 18:00:32 +0200 Subject: [PATCH 20/68] kill old non-OCS Controller Signed-off-by: Arthur Schiwon --- apps/workflowengine/appinfo/routes.php | 4 - .../composer/composer/autoload_classmap.php | 1 - .../composer/composer/autoload_static.php | 1 - .../lib/Controller/FlowOperations.php | 128 ------------------ 4 files changed, 134 deletions(-) delete mode 100644 apps/workflowengine/lib/Controller/FlowOperations.php diff --git a/apps/workflowengine/appinfo/routes.php b/apps/workflowengine/appinfo/routes.php index c650bee4cf2..3798c2a852c 100644 --- a/apps/workflowengine/appinfo/routes.php +++ b/apps/workflowengine/appinfo/routes.php @@ -21,10 +21,6 @@ return [ 'routes' => [ - ['name' => 'flowOperations#getOperations', 'url' => '/operations', 'verb' => 'GET'], - ['name' => 'flowOperations#addOperation', 'url' => '/operations', 'verb' => 'POST'], - ['name' => 'flowOperations#updateOperation', 'url' => '/operations/{id}', 'verb' => 'PUT'], - ['name' => 'flowOperations#deleteOperation', 'url' => '/operations/{id}', 'verb' => 'DELETE'], ['name' => 'requestTime#getTimezones', 'url' => '/timezones', 'verb' => 'GET'], ], 'ocs-resources' => [ diff --git a/apps/workflowengine/composer/composer/autoload_classmap.php b/apps/workflowengine/composer/composer/autoload_classmap.php index 637f1a8abff..d0331ea4d4e 100644 --- a/apps/workflowengine/composer/composer/autoload_classmap.php +++ b/apps/workflowengine/composer/composer/autoload_classmap.php @@ -19,7 +19,6 @@ return array( 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => $baseDir . '/../lib/Check/UserGroupMembership.php', 'OCA\\WorkflowEngine\\Command\\Index' => $baseDir . '/../lib/Command/Index.php', 'OCA\\WorkflowEngine\\Controller\\AWorkflowController' => $baseDir . '/../lib/Controller/AWorkflowController.php', - 'OCA\\WorkflowEngine\\Controller\\FlowOperations' => $baseDir . '/../lib/Controller/FlowOperations.php', 'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => $baseDir . '/../lib/Controller/GlobalWorkflowsController.php', 'OCA\\WorkflowEngine\\Controller\\RequestTime' => $baseDir . '/../lib/Controller/RequestTime.php', 'OCA\\WorkflowEngine\\Controller\\UserWorkflowsController' => $baseDir . '/../lib/Controller/UserWorkflowsController.php', diff --git a/apps/workflowengine/composer/composer/autoload_static.php b/apps/workflowengine/composer/composer/autoload_static.php index edf3f3b0518..156f49a69ac 100644 --- a/apps/workflowengine/composer/composer/autoload_static.php +++ b/apps/workflowengine/composer/composer/autoload_static.php @@ -34,7 +34,6 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => __DIR__ . '/..' . '/../lib/Check/UserGroupMembership.php', 'OCA\\WorkflowEngine\\Command\\Index' => __DIR__ . '/..' . '/../lib/Command/Index.php', 'OCA\\WorkflowEngine\\Controller\\AWorkflowController' => __DIR__ . '/..' . '/../lib/Controller/AWorkflowController.php', - 'OCA\\WorkflowEngine\\Controller\\FlowOperations' => __DIR__ . '/..' . '/../lib/Controller/FlowOperations.php', 'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => __DIR__ . '/..' . '/../lib/Controller/GlobalWorkflowsController.php', 'OCA\\WorkflowEngine\\Controller\\RequestTime' => __DIR__ . '/..' . '/../lib/Controller/RequestTime.php', 'OCA\\WorkflowEngine\\Controller\\UserWorkflowsController' => __DIR__ . '/..' . '/../lib/Controller/UserWorkflowsController.php', diff --git a/apps/workflowengine/lib/Controller/FlowOperations.php b/apps/workflowengine/lib/Controller/FlowOperations.php deleted file mode 100644 index 7ed2604ce06..00000000000 --- a/apps/workflowengine/lib/Controller/FlowOperations.php +++ /dev/null @@ -1,128 +0,0 @@ - - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -namespace OCA\WorkflowEngine\Controller; - -use OCA\WorkflowEngine\Manager; -use OCP\AppFramework\Controller; -use OCP\AppFramework\Http; -use OCP\AppFramework\Http\JSONResponse; -use OCP\IRequest; - -class FlowOperations extends Controller { - - /** @var Manager */ - protected $manager; - - /** - * @param IRequest $request - * @param Manager $manager - */ - public function __construct(IRequest $request, Manager $manager) { - parent::__construct('workflowengine', $request); - $this->manager = $manager; - } - - /** - * @NoCSRFRequired - * - * @param string $class - * @return JSONResponse - */ - public function getOperations($class) { - $operations = $this->manager->getOperations($class); - - foreach ($operations as &$operation) { - $operation = $this->prepareOperation($operation); - } - - return new JSONResponse($operations); - } - - /** - * @PasswordConfirmationRequired - * - * @param string $class - * @param string $name - * @param array[] $checks - * @param string $operation - * @return JSONResponse The added element - */ - public function addOperation($class, $name, $checks, $operation) { - try { - $operation = $this->manager->addOperation($class, $name, $checks, $operation); - $operation = $this->prepareOperation($operation); - return new JSONResponse($operation); - } catch (\UnexpectedValueException $e) { - return new JSONResponse($e->getMessage(), Http::STATUS_BAD_REQUEST); - } - } - - /** - * @PasswordConfirmationRequired - * - * @param int $id - * @param string $name - * @param array[] $checks - * @param string $operation - * @return JSONResponse The updated element - */ - public function updateOperation($id, $name, $checks, $operation) { - try { - $operation = $this->manager->updateOperation($id, $name, $checks, $operation); - $operation = $this->prepareOperation($operation); - return new JSONResponse($operation); - } catch (\UnexpectedValueException $e) { - return new JSONResponse($e->getMessage(), Http::STATUS_BAD_REQUEST); - } - } - - /** - * @PasswordConfirmationRequired - * - * @param int $id - * @return JSONResponse - */ - public function deleteOperation($id) { - $deleted = $this->manager->deleteOperation((int) $id); - return new JSONResponse($deleted); - } - - /** - * @param array $operation - * @return array - */ - protected function prepareOperation(array $operation) { - $checkIds = json_decode($operation['checks'], true); - $checks = $this->manager->getChecks($checkIds); - - $operation['checks'] = []; - foreach ($checks as $check) { - // Remove internal values - unset($check['id']); - unset($check['hash']); - - $operation['checks'][] = $check; - } - - return $operation; - } -} From 20901c59d47e74179a04ca7938afe1ff131f6576 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 3 Sep 2019 12:30:10 +0200 Subject: [PATCH 21/68] emit file events via Dispatcher, too another step to get rid of hooks and emitters Signed-off-by: Arthur Schiwon --- lib/private/Files/Node/File.php | 2 +- lib/private/Files/Node/Folder.php | 14 +++++--------- lib/private/Files/Node/HookConnector.php | 20 +++++++++++++++++++- lib/private/Files/Node/Node.php | 24 ++++++++++++++---------- lib/private/Server.php | 2 +- 5 files changed, 40 insertions(+), 22 deletions(-) diff --git a/lib/private/Files/Node/File.php b/lib/private/Files/Node/File.php index a3eabbcc446..b504c7a29da 100644 --- a/lib/private/Files/Node/File.php +++ b/lib/private/Files/Node/File.php @@ -119,7 +119,7 @@ class File extends Node implements \OCP\Files\File { $fileInfo = $this->getFileInfo(); $this->view->unlink($this->path); $nonExisting = new NonExistingFile($this->root, $this->view, $this->path, $fileInfo); - $this->root->emit('\OC\Files', 'postDelete', array($nonExisting)); + $this->sendHooks(['postDelete'], [$nonExisting]); $this->exists = false; $this->fileInfo = null; } else { diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 19f04048779..8b2a93ffdc2 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -156,14 +156,12 @@ class Folder extends Node implements \OCP\Files\Folder { if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { $fullPath = $this->getFullPath($path); $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath); - $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); - $this->root->emit('\OC\Files', 'preCreate', array($nonExisting)); + $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]); if(!$this->view->mkdir($fullPath)) { throw new NotPermittedException('Could not create folder'); } $node = new Folder($this->root, $this->view, $fullPath); - $this->root->emit('\OC\Files', 'postWrite', array($node)); - $this->root->emit('\OC\Files', 'postCreate', array($node)); + $this->sendHooks(['postWrite', 'postCreate'], [$node]); return $node; } else { throw new NotPermittedException('No create permission for folder'); @@ -179,14 +177,12 @@ class Folder extends Node implements \OCP\Files\Folder { if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { $fullPath = $this->getFullPath($path); $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath); - $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); - $this->root->emit('\OC\Files', 'preCreate', array($nonExisting)); + $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]); if (!$this->view->touch($fullPath)) { throw new NotPermittedException('Could not create path'); } $node = new File($this->root, $this->view, $fullPath); - $this->root->emit('\OC\Files', 'postWrite', array($node)); - $this->root->emit('\OC\Files', 'postCreate', array($node)); + $this->sendHooks(['postWrite', 'postCreate'], [$node]); return $node; } throw new NotPermittedException('No create permission for path'); @@ -341,7 +337,7 @@ class Folder extends Node implements \OCP\Files\Folder { $fileInfo = $this->getFileInfo(); $this->view->rmdir($this->path); $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo); - $this->root->emit('\OC\Files', 'postDelete', array($nonExisting)); + $this->sendHooks(['postDelete'], [$nonExisting]); $this->exists = false; } else { throw new NotPermittedException('No delete permission for path'); diff --git a/lib/private/Files/Node/HookConnector.php b/lib/private/Files/Node/HookConnector.php index f5adcde4a00..4783a71b07b 100644 --- a/lib/private/Files/Node/HookConnector.php +++ b/lib/private/Files/Node/HookConnector.php @@ -26,6 +26,8 @@ use OCP\Files\FileInfo; use OC\Files\Filesystem; use OC\Files\View; use OCP\Util; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; class HookConnector { /** @@ -42,6 +44,8 @@ class HookConnector { * @var FileInfo[] */ private $deleteMetaCache = []; + /** @var EventDispatcherInterface */ + private $dispatcher; /** * HookConnector constructor. @@ -49,9 +53,10 @@ class HookConnector { * @param Root $root * @param View $view */ - public function __construct(Root $root, View $view) { + public function __construct(Root $root, View $view, EventDispatcherInterface $dispatcher) { $this->root = $root; $this->view = $view; + $this->dispatcher = $dispatcher; } public function viewToNode() { @@ -79,72 +84,85 @@ class HookConnector { public function write($arguments) { $node = $this->getNodeForPath($arguments['path']); $this->root->emit('\OC\Files', 'preWrite', [$node]); + $this->dispatcher->dispatch('\OCP\Files::preWrite', new GenericEvent($node)); } public function postWrite($arguments) { $node = $this->getNodeForPath($arguments['path']); $this->root->emit('\OC\Files', 'postWrite', [$node]); + $this->dispatcher->dispatch('\OCP\Files::postWrite', new GenericEvent($node)); } public function create($arguments) { $node = $this->getNodeForPath($arguments['path']); $this->root->emit('\OC\Files', 'preCreate', [$node]); + $this->dispatcher->dispatch('\OCP\Files::preCreate', new GenericEvent($node)); } public function postCreate($arguments) { $node = $this->getNodeForPath($arguments['path']); $this->root->emit('\OC\Files', 'postCreate', [$node]); + $this->dispatcher->dispatch('\OCP\Files::postCreate', new GenericEvent($node)); } public function delete($arguments) { $node = $this->getNodeForPath($arguments['path']); $this->deleteMetaCache[$node->getPath()] = $node->getFileInfo(); $this->root->emit('\OC\Files', 'preDelete', [$node]); + $this->dispatcher->dispatch('\OCP\Files::preDelete', new GenericEvent($node)); } public function postDelete($arguments) { $node = $this->getNodeForPath($arguments['path']); unset($this->deleteMetaCache[$node->getPath()]); $this->root->emit('\OC\Files', 'postDelete', [$node]); + $this->dispatcher->dispatch('\OCP\Files::postDelete', new GenericEvent($node)); } public function touch($arguments) { $node = $this->getNodeForPath($arguments['path']); $this->root->emit('\OC\Files', 'preTouch', [$node]); + $this->dispatcher->dispatch('\OCP\Files::preTouch', new GenericEvent($node)); } public function postTouch($arguments) { $node = $this->getNodeForPath($arguments['path']); $this->root->emit('\OC\Files', 'postTouch', [$node]); + $this->dispatcher->dispatch('\OCP\Files::postTouch', new GenericEvent($node)); } public function rename($arguments) { $source = $this->getNodeForPath($arguments['oldpath']); $target = $this->getNodeForPath($arguments['newpath']); $this->root->emit('\OC\Files', 'preRename', [$source, $target]); + $this->dispatcher->dispatch('\OCP\Files::preRename', new GenericEvent([$source, $target])); } public function postRename($arguments) { $source = $this->getNodeForPath($arguments['oldpath']); $target = $this->getNodeForPath($arguments['newpath']); $this->root->emit('\OC\Files', 'postRename', [$source, $target]); + $this->dispatcher->dispatch('\OCP\Files::postRename', new GenericEvent([$source, $target])); } public function copy($arguments) { $source = $this->getNodeForPath($arguments['oldpath']); $target = $this->getNodeForPath($arguments['newpath']); $this->root->emit('\OC\Files', 'preCopy', [$source, $target]); + $this->dispatcher->dispatch('\OCP\Files::preCopy', new GenericEvent([$source, $target])); } public function postCopy($arguments) { $source = $this->getNodeForPath($arguments['oldpath']); $target = $this->getNodeForPath($arguments['newpath']); $this->root->emit('\OC\Files', 'postCopy', [$source, $target]); + $this->dispatcher->dispatch('\OCP\Files::postCopy', new GenericEvent([$source, $target])); } public function read($arguments) { $node = $this->getNodeForPath($arguments['path']); $this->root->emit('\OC\Files', 'read', [$node]); + $this->dispatcher->dispatch('\OCP\Files::read', new GenericEvent([$node])); } private function getNodeForPath($path) { diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index dc025b79575..c440dd4a8f1 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -33,6 +33,7 @@ use OCP\Files\FileInfo; use OCP\Files\InvalidPathException; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use Symfony\Component\EventDispatcher\GenericEvent; // FIXME: this class really should be abstract class Node implements \OCP\Files\Node { @@ -104,9 +105,12 @@ class Node implements \OCP\Files\Node { /** * @param string[] $hooks */ - protected function sendHooks($hooks) { + protected function sendHooks($hooks, array $args = null) { + $args = !empty($args) ? $args : [$this]; + $dispatcher = \OC::$server->getEventDispatcher(); foreach ($hooks as $hook) { - $this->root->emit('\OC\Files', $hook, array($this)); + $this->root->emit('\OC\Files', $hook, $args); + $dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args)); } } @@ -394,14 +398,14 @@ class Node implements \OCP\Files\Node { $parent = $this->root->get(dirname($targetPath)); if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { $nonExisting = $this->createNonExistingNode($targetPath); - $this->root->emit('\OC\Files', 'preCopy', [$this, $nonExisting]); - $this->root->emit('\OC\Files', 'preWrite', [$nonExisting]); + $this->sendHooks(['preCopy'], [$this, $nonExisting]); + $this->sendHooks(['preWrite'], [$nonExisting]); if (!$this->view->copy($this->path, $targetPath)) { throw new NotPermittedException('Could not copy ' . $this->path . ' to ' . $targetPath); } $targetNode = $this->root->get($targetPath); - $this->root->emit('\OC\Files', 'postCopy', [$this, $targetNode]); - $this->root->emit('\OC\Files', 'postWrite', [$targetNode]); + $this->sendHooks(['postCopy'], [$this, $targetNode]); + $this->sendHooks(['postWrite'], [$targetNode]); return $targetNode; } else { throw new NotPermittedException('No permission to copy to path ' . $targetPath); @@ -425,14 +429,14 @@ class Node implements \OCP\Files\Node { ) ) { $nonExisting = $this->createNonExistingNode($targetPath); - $this->root->emit('\OC\Files', 'preRename', [$this, $nonExisting]); - $this->root->emit('\OC\Files', 'preWrite', [$nonExisting]); + $this->sendHooks(['preRename'], [$this, $nonExisting]); + $this->sendHooks(['preWrite'], [$nonExisting]); if (!$this->view->rename($this->path, $targetPath)) { throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath); } $targetNode = $this->root->get($targetPath); - $this->root->emit('\OC\Files', 'postRename', [$this, $targetNode]); - $this->root->emit('\OC\Files', 'postWrite', [$targetNode]); + $this->sendHooks(['postRename'], [$this, $targetNode]); + $this->sendHooks(['postWrite'], [$targetNode]); $this->path = $targetPath; return $targetNode; } else { diff --git a/lib/private/Server.php b/lib/private/Server.php index bce4f0feaef..433ee044fa4 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -298,7 +298,7 @@ class Server extends ServerContainer implements IServerContainer { $this->getLogger(), $this->getUserManager() ); - $connector = new HookConnector($root, $view); + $connector = new HookConnector($root, $view, $c->getEventDispatcher()); $connector->viewToNode(); $previewConnector = new \OC\Preview\WatcherConnector($root, $c->getSystemConfig()); From bed518e8abd60875f4fe3d2b0e15fd87b489b6d4 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 3 Sep 2019 12:31:29 +0200 Subject: [PATCH 22/68] introduce GenericEntityEvent and adapt File entity Signed-off-by: Arthur Schiwon --- .../composer/composer/autoload_classmap.php | 2 - .../composer/composer/autoload_static.php | 2 - apps/workflowengine/lib/Entity/File.php | 17 +++---- .../lib/Entity/IEntityEmitterEvent.php | 34 ------------- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + .../WorkflowEngine/GenericEntityEvent.php | 50 +++++++++++-------- 7 files changed, 39 insertions(+), 68 deletions(-) delete mode 100644 apps/workflowengine/lib/Entity/IEntityEmitterEvent.php rename apps/workflowengine/lib/Entity/GenericEntityEmitterEvent.php => lib/public/WorkflowEngine/GenericEntityEvent.php (56%) diff --git a/apps/workflowengine/composer/composer/autoload_classmap.php b/apps/workflowengine/composer/composer/autoload_classmap.php index d0331ea4d4e..4dab03642dd 100644 --- a/apps/workflowengine/composer/composer/autoload_classmap.php +++ b/apps/workflowengine/composer/composer/autoload_classmap.php @@ -23,8 +23,6 @@ return array( 'OCA\\WorkflowEngine\\Controller\\RequestTime' => $baseDir . '/../lib/Controller/RequestTime.php', 'OCA\\WorkflowEngine\\Controller\\UserWorkflowsController' => $baseDir . '/../lib/Controller/UserWorkflowsController.php', 'OCA\\WorkflowEngine\\Entity\\File' => $baseDir . '/../lib/Entity/File.php', - 'OCA\\WorkflowEngine\\Entity\\GenericEntityEmitterEvent' => $baseDir . '/../lib/Entity/GenericEntityEmitterEvent.php', - 'OCA\\WorkflowEngine\\Entity\\IEntityEmitterEvent' => $baseDir . '/../lib/Entity/IEntityEmitterEvent.php', 'OCA\\WorkflowEngine\\Helper\\ScopeContext' => $baseDir . '/../lib/Helper/ScopeContext.php', 'OCA\\WorkflowEngine\\Manager' => $baseDir . '/../lib/Manager.php', 'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => $baseDir . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php', diff --git a/apps/workflowengine/composer/composer/autoload_static.php b/apps/workflowengine/composer/composer/autoload_static.php index 156f49a69ac..38b1a5b0e76 100644 --- a/apps/workflowengine/composer/composer/autoload_static.php +++ b/apps/workflowengine/composer/composer/autoload_static.php @@ -38,8 +38,6 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Controller\\RequestTime' => __DIR__ . '/..' . '/../lib/Controller/RequestTime.php', 'OCA\\WorkflowEngine\\Controller\\UserWorkflowsController' => __DIR__ . '/..' . '/../lib/Controller/UserWorkflowsController.php', 'OCA\\WorkflowEngine\\Entity\\File' => __DIR__ . '/..' . '/../lib/Entity/File.php', - 'OCA\\WorkflowEngine\\Entity\\GenericEntityEmitterEvent' => __DIR__ . '/..' . '/../lib/Entity/GenericEntityEmitterEvent.php', - 'OCA\\WorkflowEngine\\Entity\\IEntityEmitterEvent' => __DIR__ . '/..' . '/../lib/Entity/IEntityEmitterEvent.php', 'OCA\\WorkflowEngine\\Helper\\ScopeContext' => __DIR__ . '/..' . '/../lib/Helper/ScopeContext.php', 'OCA\\WorkflowEngine\\Manager' => __DIR__ . '/..' . '/../lib/Manager.php', 'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => __DIR__ . '/..' . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php', diff --git a/apps/workflowengine/lib/Entity/File.php b/apps/workflowengine/lib/Entity/File.php index d4f41625e4e..b420217c4b6 100644 --- a/apps/workflowengine/lib/Entity/File.php +++ b/apps/workflowengine/lib/Entity/File.php @@ -24,9 +24,9 @@ declare(strict_types=1); namespace OCA\WorkflowEngine\Entity; -use OCP\Files\IRootFolder; use OCP\IL10N; use OCP\IURLGenerator; +use OCP\WorkflowEngine\GenericEntityEvent; use OCP\WorkflowEngine\IEntity; class File implements IEntity { @@ -50,15 +50,14 @@ class File implements IEntity { } public function getEvents(): array { - $emitterClass = IRootFolder::class; - $slot = '\OC\Files'; + $namespace = '\OCP\Files::'; return [ - new GenericEntityEmitterEvent($emitterClass, $slot, 'postCreate', $this->l10n->t('File created')), - new GenericEntityEmitterEvent($emitterClass, $slot, 'postWrite', $this->l10n->t('File updated')), - new GenericEntityEmitterEvent($emitterClass, $slot, 'postRename', $this->l10n->t('File renamed')), - new GenericEntityEmitterEvent($emitterClass, $slot, 'postDelete', $this->l10n->t('File deleted')), - new GenericEntityEmitterEvent($emitterClass, $slot, 'postTouch', $this->l10n->t('File accessed')), - new GenericEntityEmitterEvent($emitterClass, $slot, 'postCopy', $this->l10n->t('File copied')), + new GenericEntityEvent($this->l10n->t('File created'), $namespace . 'postCreate' ), + new GenericEntityEvent($this->l10n->t('File updated'), $namespace . 'postWrite' ), + new GenericEntityEvent($this->l10n->t('File renamed'), $namespace . 'postRename' ), + new GenericEntityEvent($this->l10n->t('File deleted'), $namespace . 'postDelete' ), + new GenericEntityEvent($this->l10n->t('File accessed'), $namespace . 'postTouch' ), + new GenericEntityEvent($this->l10n->t('File copied'), $namespace . 'postCopy' ), ]; } } diff --git a/apps/workflowengine/lib/Entity/IEntityEmitterEvent.php b/apps/workflowengine/lib/Entity/IEntityEmitterEvent.php deleted file mode 100644 index 7e2c802fe76..00000000000 --- a/apps/workflowengine/lib/Entity/IEntityEmitterEvent.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * @author Arthur Schiwon - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -namespace OCA\WorkflowEngine\Entity; - - -use OCP\WorkflowEngine\IEntityEvent; - -interface IEntityEmitterEvent extends IEntityEvent { - public function getEmitterClassName(): string; - - public function getSlot(): string; -} diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index f8fb8b76d6b..6673cbe70a7 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -438,6 +438,7 @@ return array( 'OCP\\User\\Backend\\ISetDisplayNameBackend' => $baseDir . '/lib/public/User/Backend/ISetDisplayNameBackend.php', 'OCP\\User\\Backend\\ISetPasswordBackend' => $baseDir . '/lib/public/User/Backend/ISetPasswordBackend.php', 'OCP\\Util' => $baseDir . '/lib/public/Util.php', + 'OCP\\WorkflowEngine\\GenericEntityEvent' => $baseDir . '/lib/public/WorkflowEngine/GenericEntityEvent.php', 'OCP\\WorkflowEngine\\ICheck' => $baseDir . '/lib/public/WorkflowEngine/ICheck.php', 'OCP\\WorkflowEngine\\IComplexOperation' => $baseDir . '/lib/public/WorkflowEngine/IComplexOperation.php', 'OCP\\WorkflowEngine\\IEntity' => $baseDir . '/lib/public/WorkflowEngine/IEntity.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index ab47f44ff8e..d4b74d5c231 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -472,6 +472,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\User\\Backend\\ISetDisplayNameBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISetDisplayNameBackend.php', 'OCP\\User\\Backend\\ISetPasswordBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISetPasswordBackend.php', 'OCP\\Util' => __DIR__ . '/../../..' . '/lib/public/Util.php', + 'OCP\\WorkflowEngine\\GenericEntityEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/GenericEntityEvent.php', 'OCP\\WorkflowEngine\\ICheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ICheck.php', 'OCP\\WorkflowEngine\\IComplexOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IComplexOperation.php', 'OCP\\WorkflowEngine\\IEntity' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntity.php', diff --git a/apps/workflowengine/lib/Entity/GenericEntityEmitterEvent.php b/lib/public/WorkflowEngine/GenericEntityEvent.php similarity index 56% rename from apps/workflowengine/lib/Entity/GenericEntityEmitterEvent.php rename to lib/public/WorkflowEngine/GenericEntityEvent.php index 60b238432c9..d72093573bf 100644 --- a/apps/workflowengine/lib/Entity/GenericEntityEmitterEvent.php +++ b/lib/public/WorkflowEngine/GenericEntityEvent.php @@ -22,38 +22,46 @@ declare(strict_types=1); * */ -namespace OCA\WorkflowEngine\Entity; +namespace OCP\WorkflowEngine; + +class GenericEntityEvent implements IEntityEvent { -class GenericEntityEmitterEvent implements IEntityEmitterEvent { - /** @var string */ - private $emitterClassName; - /** @var string */ - private $eventName; /** @var string */ private $displayName; /** @var string */ - private $slot; - - public function __construct(string $emitterClassName, string $slot, string $eventName, string $displayName) { - $this->emitterClassName = $emitterClassName; - $this->eventName = $eventName; - $this->displayName = $displayName; - $this->slot = $slot; - } + private $eventName; - public function getEmitterClassName(): string { - return $this->emitterClassName; - } + public function __construct(string $displayName, string $eventName) { + if(trim($displayName) === '') { + throw new \InvalidArgumentException('DisplayName must not be empty'); + } + if(trim($eventName) === '') { + throw new \InvalidArgumentException('EventName must not be empty'); + } - public function getSlot(): string { - return $this->slot; + $this->displayName = trim($displayName); + $this->eventName = trim($eventName); } + /** + * returns a translated name to be presented in the web interface. + * + * Example: "created" (en), "kreita" (eo) + * + * @since 18.0.0 + */ public function getDisplayName(): string { - return $this->displayName; + return $this->getDisplayName(); } + /** + * returns the event name that is emitted by the EventDispatcher, e.g.: + * + * Example: "OCA\MyApp\Factory\Cats::postCreated" + * + * @since 18.0.0 + */ public function getEventName(): string { - return $this->eventName; + return $this->getEventName(); } } From d2c8b939d58239a99163445b83d80873932a5514 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 3 Sep 2019 12:42:57 +0200 Subject: [PATCH 23/68] WFE as proxy listen to relevent events and forwards them Signed-off-by: Arthur Schiwon --- apps/workflowengine/appinfo/app.php | 1 + .../lib/AppInfo/Application.php | 36 ++++++++++++++++--- apps/workflowengine/lib/Manager.php | 27 ++++++++++++++ lib/public/WorkflowEngine/IOperation.php | 15 ++++++++ 4 files changed, 75 insertions(+), 4 deletions(-) diff --git a/apps/workflowengine/appinfo/app.php b/apps/workflowengine/appinfo/app.php index f6f22ce9488..d99c5892231 100644 --- a/apps/workflowengine/appinfo/app.php +++ b/apps/workflowengine/appinfo/app.php @@ -21,3 +21,4 @@ $application = new \OCA\WorkflowEngine\AppInfo\Application(); $application->registerHooksAndListeners(); +$application->registerRuleListeners(); diff --git a/apps/workflowengine/lib/AppInfo/Application.php b/apps/workflowengine/lib/AppInfo/Application.php index 358353b6623..e691c53d528 100644 --- a/apps/workflowengine/lib/AppInfo/Application.php +++ b/apps/workflowengine/lib/AppInfo/Application.php @@ -21,27 +21,36 @@ namespace OCA\WorkflowEngine\AppInfo; +use OCA\WorkflowEngine\Manager; use OCP\Template; use OCA\WorkflowEngine\Controller\RequestTime; -use OCA\WorkflowEngine\Controller\FlowOperations; +use OCP\WorkflowEngine\IOperation; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; class Application extends \OCP\AppFramework\App { const APP_ID = 'workflowengine'; + /** @var EventDispatcherInterface */ + protected $dispatcher; + /** @var Manager */ + protected $manager; + public function __construct() { parent::__construct(self::APP_ID); - $this->getContainer()->registerAlias('FlowOperationsController', FlowOperations::class); $this->getContainer()->registerAlias('RequestTimeController', RequestTime::class); + + $this->dispatcher = $this->getContainer()->getServer()->getEventDispatcher(); + $this->manager = $this->getContainer()->query(Manager::class); } /** * Register all hooks and listeners */ public function registerHooksAndListeners() { - $dispatcher = $this->getContainer()->getServer()->getEventDispatcher(); - $dispatcher->addListener( + $this->dispatcher->addListener( 'OCP\WorkflowEngine::loadAdditionalSettingScripts', function() { if (!function_exists('style')) { @@ -68,4 +77,23 @@ class Application extends \OCP\AppFramework\App { -100 ); } + + public function registerRuleListeners() { + $configuredEvents = $this->manager->getAllConfiguredEvents(); + + foreach ($configuredEvents as $operationClass => $events) { + foreach ($events as $entityClass => $eventNames) { + array_map(function (string $eventName) use ($operationClass) { + $this->dispatcher->addListener( + $eventName, + function (GenericEvent $event) use ($eventName, $operationClass) { + /** @var IOperation $operation */ + $operation = $this->getContainer()->query($operationClass); + $operation->onEvent($eventName, $event); + } + ); + }, $eventNames); + } + } + } } diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index 7ca24a754dc..8782fd3efef 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -182,6 +182,33 @@ class Manager implements IManager, IEntityAware { throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class'])); } } + + public function getAllConfiguredEvents() { + $query = $this->connection->getQueryBuilder(); + + $query->selectDistinct('class') + ->addSelect('entity', 'events') + ->from('flow_operations') + ->where($query->expr()->neq('events', $query->createNamedParameter('[]'), IQueryBuilder::PARAM_STR)); + + $result = $query->execute(); + $operations = []; + while($row = $result->fetch()) { + $eventNames = \json_decode($row['events']); + + $operation = $row['class']; + $entity = $row['entity']; + + $operations[$operation] = $operations[$row['class']] ?? []; + $operations[$operation][$entity] = $operations[$operation][$entity] ?? []; + + $operations[$operation][$entity] = array_unique(array_merge($operations[$operation][$entity], $eventNames)); + } + $result->closeCursor(); + + return $operations; + } + public function getAllOperations(ScopeContext $scopeContext): array { if(isset($this->operations[$scopeContext->getHash()])) { return $this->operations[$scopeContext->getHash()]; diff --git a/lib/public/WorkflowEngine/IOperation.php b/lib/public/WorkflowEngine/IOperation.php index 0862588e86f..8bba92351a2 100644 --- a/lib/public/WorkflowEngine/IOperation.php +++ b/lib/public/WorkflowEngine/IOperation.php @@ -23,6 +23,8 @@ namespace OCP\WorkflowEngine; +use Symfony\Component\EventDispatcher\GenericEvent; + /** * Interface IOperation * @@ -84,4 +86,17 @@ interface IOperation { * @since 9.1 */ public function validateOperation(string $name, array $checks, string $operation): void; + + /** + * Is being called by the workflow engine when an event was triggered that + * is configured for this operation. An evaluation whether the event + * qualifies for this operation to run has still to be done by the + * implementor. + * + * If the implementor is an IComplexOpe ration, this method will not be + * called automatically. It can be used or left as no-op by the implementor. + * + * @since 18.0.0 + */ + public function onEvent(string $eventName, GenericEvent $event): void; } From c2a52813e2cfadc43b096927ee9d9db3d9ac5c84 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 5 Sep 2019 15:52:11 +0200 Subject: [PATCH 24/68] extends ICheck with scope and entity support, provide them as initialState Signed-off-by: Arthur Schiwon --- .../lib/Check/AbstractStringCheck.php | 11 ++++ .../workflowengine/lib/Check/FileMimeType.php | 5 ++ apps/workflowengine/lib/Check/FileName.php | 9 +++ apps/workflowengine/lib/Check/FileSize.php | 9 +++ .../lib/Check/FileSystemTags.php | 9 +++ apps/workflowengine/lib/Check/RequestTime.php | 4 ++ .../lib/Check/RequestUserAgent.php | 4 ++ apps/workflowengine/lib/Manager.php | 56 +++++++++++++++++-- .../workflowengine/lib/Settings/ASettings.php | 21 +++++++ lib/public/WorkflowEngine/ICheck.php | 24 ++++++++ lib/public/WorkflowEngine/IManager.php | 17 ++++-- 11 files changed, 160 insertions(+), 9 deletions(-) diff --git a/apps/workflowengine/lib/Check/AbstractStringCheck.php b/apps/workflowengine/lib/Check/AbstractStringCheck.php index 0fd728e3496..7a385729e09 100644 --- a/apps/workflowengine/lib/Check/AbstractStringCheck.php +++ b/apps/workflowengine/lib/Check/AbstractStringCheck.php @@ -25,6 +25,7 @@ namespace OCA\WorkflowEngine\Check; use OCP\Files\Storage\IStorage; use OCP\IL10N; use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IManager; abstract class AbstractStringCheck implements ICheck { @@ -101,6 +102,16 @@ abstract class AbstractStringCheck implements ICheck { } } + public function supportedEntities(): array { + // universal by default + return []; + } + + public function isAvailableForScope(int $scope): bool { + // admin only by default + return $scope === IManager::SCOPE_ADMIN; + } + /** * @param string $pattern * @param string $subject diff --git a/apps/workflowengine/lib/Check/FileMimeType.php b/apps/workflowengine/lib/Check/FileMimeType.php index 5f572f5aa9d..6d51992fdd0 100644 --- a/apps/workflowengine/lib/Check/FileMimeType.php +++ b/apps/workflowengine/lib/Check/FileMimeType.php @@ -22,6 +22,7 @@ namespace OCA\WorkflowEngine\Check; +use OCA\WorkflowEngine\Entity\File; use OCP\Files\IMimeTypeDetector; use OCP\Files\Storage\IStorage; use OCP\IL10N; @@ -195,4 +196,8 @@ class FileMimeType extends AbstractStringCheck { strpos($this->request->getPathInfo(), '/webdav/') === 0 ); } + + public function supportedEntities(): array { + return [ File::class ]; + } } diff --git a/apps/workflowengine/lib/Check/FileName.php b/apps/workflowengine/lib/Check/FileName.php index c6afbf7afad..d9b63ebfed1 100644 --- a/apps/workflowengine/lib/Check/FileName.php +++ b/apps/workflowengine/lib/Check/FileName.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace OCA\WorkflowEngine\Check; +use OCA\WorkflowEngine\Entity\File; use OCP\Files\Storage\IStorage; use OCP\IL10N; use OCP\IRequest; @@ -75,4 +76,12 @@ class FileName extends AbstractStringCheck { } return parent::executeStringCheck($operator, $checkValue, $actualValue); } + + public function supportedEntities(): array { + return [ File::class ]; + } + + public function isAvailableForScope(int $scope): bool { + return true; + } } diff --git a/apps/workflowengine/lib/Check/FileSize.php b/apps/workflowengine/lib/Check/FileSize.php index 7e48f0f6038..06a45bdcd41 100644 --- a/apps/workflowengine/lib/Check/FileSize.php +++ b/apps/workflowengine/lib/Check/FileSize.php @@ -22,6 +22,7 @@ namespace OCA\WorkflowEngine\Check; +use OCA\WorkflowEngine\Entity\File; use OCP\Files\Storage\IStorage; use OCP\IL10N; use OCP\IRequest; @@ -116,4 +117,12 @@ class FileSize implements ICheck { $this->size = $size; return $this->size; } + + public function supportedEntities(): array { + return [ File::class ]; + } + + public function isAvailableForScope(int $scope): bool { + return true; + } } diff --git a/apps/workflowengine/lib/Check/FileSystemTags.php b/apps/workflowengine/lib/Check/FileSystemTags.php index 4a2b87fd53e..12285c9b33e 100644 --- a/apps/workflowengine/lib/Check/FileSystemTags.php +++ b/apps/workflowengine/lib/Check/FileSystemTags.php @@ -22,6 +22,7 @@ namespace OCA\WorkflowEngine\Check; +use OCA\WorkflowEngine\Entity\File; use OCP\Files\Cache\ICache; use OCP\Files\IHomeStorage; use OCP\Files\Storage\IStorage; @@ -166,4 +167,12 @@ class FileSystemTags implements ICheck { $dir = dirname($path); return $dir === '.' ? '' : $dir; } + + public function supportedEntities(): array { + return [ File::class ]; + } + + public function isAvailableForScope(int $scope): bool { + return true; + } } diff --git a/apps/workflowengine/lib/Check/RequestTime.php b/apps/workflowengine/lib/Check/RequestTime.php index 2aa79e77673..0f5322b4759 100644 --- a/apps/workflowengine/lib/Check/RequestTime.php +++ b/apps/workflowengine/lib/Check/RequestTime.php @@ -126,4 +126,8 @@ class RequestTime implements ICheck { throw new \UnexpectedValueException($this->l->t('The given end time is invalid'), 4); } } + + public function isAvailableForScope(int $scope): bool { + return true; + } } diff --git a/apps/workflowengine/lib/Check/RequestUserAgent.php b/apps/workflowengine/lib/Check/RequestUserAgent.php index 4191a2f3412..2b2e3015557 100644 --- a/apps/workflowengine/lib/Check/RequestUserAgent.php +++ b/apps/workflowengine/lib/Check/RequestUserAgent.php @@ -79,4 +79,8 @@ class RequestUserAgent extends AbstractStringCheck { protected function getActualValue() { return (string) $this->request->getHeader('User-Agent'); } + + public function isAvailableForScope(int $scope): bool { + return true; + } } diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index 8782fd3efef..3f01b890a52 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -21,10 +21,18 @@ namespace OCA\WorkflowEngine; - use OC\Files\Storage\Wrapper\Jail; use Doctrine\DBAL\DBALException; use OC\Cache\CappedMemoryCache; +use OCA\WorkflowEngine\Check\FileMimeType; +use OCA\WorkflowEngine\Check\FileName; +use OCA\WorkflowEngine\Check\FileSize; +use OCA\WorkflowEngine\Check\FileSystemTags; +use OCA\WorkflowEngine\Check\RequestRemoteAddress; +use OCA\WorkflowEngine\Check\RequestTime; +use OCA\WorkflowEngine\Check\RequestURL; +use OCA\WorkflowEngine\Check\RequestUserAgent; +use OCA\WorkflowEngine\Check\UserGroupMembership; use OCA\WorkflowEngine\Entity\File; use OCA\WorkflowEngine\Helper\ScopeContext; use OCP\AppFramework\QueryException; @@ -80,6 +88,9 @@ class Manager implements IManager, IEntityAware { /** @var IOperation[] */ protected $registeredOperators = []; + /** @var ICheck[] */ + protected $registeredChecks = []; + /** @var ILogger */ protected $logger; @@ -510,6 +521,12 @@ class Manager implements IManager, IEntityAware { throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class])); } + if (!empty($instance->supportedEntities()) + && !in_array(get_class($entity), $instance->supportedEntities()) + ) { + throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class])); + } + $instance->validateCheck($check['operator'], $check['value']); } } @@ -653,11 +670,14 @@ class Manager implements IManager, IEntityAware { } /** - * Listen to 'OCP/WorkflowEngine::registerEntities' at the EventDispatcher - * for registering your entities - * - * @since 18.0.0 + * @return ICheck[] */ + public function getCheckList(): array { + $this->eventDispatcher->dispatch(IManager::EVENT_NAME_REG_CHECK, new GenericEvent($this)); + + return array_merge($this->getBuildInChecks(), $this->registeredChecks); + } + public function registerEntity(IEntity $entity): void { $this->registeredEntities[get_class($entity)] = $entity; } @@ -666,6 +686,10 @@ class Manager implements IManager, IEntityAware { $this->registeredOperators[get_class($operator)] = $operator; } + public function registerCheck(ICheck $check): void { + $this->registeredChecks[get_class($check)] = $check; + } + /** * @return IEntity[] */ @@ -693,4 +717,26 @@ class Manager implements IManager, IEntityAware { return []; } } + + /** + * @return IEntity[] + */ + protected function getBuildInChecks(): array { + try { + return [ + $this->container->query(FileMimeType::class), + $this->container->query(FileName::class), + $this->container->query(FileSize::class), + $this->container->query(FileSystemTags::class), + $this->container->query(RequestRemoteAddress::class), + $this->container->query(RequestTime::class), + $this->container->query(RequestURL::class), + $this->container->query(RequestUserAgent::class), + $this->container->query(UserGroupMembership::class), + ]; + } catch (QueryException $e) { + $this->logger->logException($e); + return []; + } + } } diff --git a/apps/workflowengine/lib/Settings/ASettings.php b/apps/workflowengine/lib/Settings/ASettings.php index 1781aaa2f55..ac2ebdb8cac 100644 --- a/apps/workflowengine/lib/Settings/ASettings.php +++ b/apps/workflowengine/lib/Settings/ASettings.php @@ -30,6 +30,7 @@ use OCP\AppFramework\Http\TemplateResponse; use OCP\IInitialStateService; use OCP\IL10N; use OCP\Settings\ISettings; +use OCP\WorkflowEngine\ICheck; use OCP\WorkflowEngine\IComplexOperation; use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IEntityEvent; @@ -94,6 +95,13 @@ abstract class ASettings implements ISettings { $this->operatorsToArray($operators) ); + $checks = $this->manager->getCheckList(); + $this->initialStateService->provideInitialState( + Application::APP_ID, + 'checks', + $this->checksToArray($checks) + ); + $this->initialStateService->provideInitialState( Application::APP_ID, 'scope', @@ -156,4 +164,17 @@ abstract class ASettings implements ISettings { ]; }, $operators); } + + private function checksToArray(array $checks) { + $checks = array_filter($checks, function(ICheck $check) { + return $check->isAvailableForScope($this->getScope()); + }); + + return array_map(function (ICheck $check) { + return [ + 'id' => get_class($check), + 'supportedEntities' => $check->supportedEntities(), + ]; + }, $checks); + } } diff --git a/lib/public/WorkflowEngine/ICheck.php b/lib/public/WorkflowEngine/ICheck.php index 1d4fc966460..92ec6f83893 100644 --- a/lib/public/WorkflowEngine/ICheck.php +++ b/lib/public/WorkflowEngine/ICheck.php @@ -55,4 +55,28 @@ interface ICheck { * @since 9.1 */ public function validateCheck($operator, $value); + + /** + * returns a list of Entities the checker supports. The values must match + * the class name of the entity. + * + * An empty result means the check is universally available. + * + * @since 18.0.0 + */ + public function supportedEntities(): array; + + /** + * returns whether the operation can be used in the requested scope. + * + * Scope IDs are defined as constants in OCP\WorkflowEngine\IManager. At + * time of writing these are SCOPE_ADMIN and SCOPE_USER. + * + * For possibly unknown future scopes the recommended behaviour is: if + * user scope is permitted, the default behaviour should return `true`, + * otherwise `false`. + * + * @since 18.0.0 + */ + public function isAvailableForScope(int $scope): bool; } diff --git a/lib/public/WorkflowEngine/IManager.php b/lib/public/WorkflowEngine/IManager.php index 8ef7a3a03e8..8be47d961e7 100644 --- a/lib/public/WorkflowEngine/IManager.php +++ b/lib/public/WorkflowEngine/IManager.php @@ -39,6 +39,7 @@ interface IManager { const EVENT_NAME_REG_OPERATION = 'OCP\WorkflowEngine::registerOperations'; const EVENT_NAME_REG_ENTITY = 'OCP\WorkflowEngine::registerEntities'; + const EVENT_NAME_REG_CHECK = 'OCP\WorkflowEngine::registerChecks'; /** * @param IStorage $storage @@ -56,18 +57,26 @@ interface IManager { public function getMatchingOperations($class, $returnFirstMatchingOperationOnly = true); /** - * Listen to 'OCP/WorkflowEngine::registerEntities' at the EventDispatcher - * for registering your entities + * Listen to `\OCP\WorkflowEngine::EVENT_NAME_REG_ENTITY` at the + * EventDispatcher for registering your entities. * * @since 18.0.0 */ public function registerEntity(IEntity $entity): void; /** - * Listen to 'OCP/WorkflowEngine::registerOperators' at the EventDispatcher - * for registering your operators + * Listen to `\OCP\WorkflowEngine::EVENT_NAME_REG_OPERATION` at the + * EventDispatcher for registering your operators. * * @since 18.0.0 */ public function registerOperation(IOperation $operator): void; + + /** + * Listen to `\OCP\WorkflowEngine::EVENT_NAME_REG_CHECK` at the + * EventDispatcher for registering your operators. + * + * @since 18.0.0 + */ + public function registerCheck(ICheck $check): void; } From 36624def94b001858b9ed80bea0557508a9fabf7 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 5 Sep 2019 15:52:38 +0200 Subject: [PATCH 25/68] add type hint for IDE Signed-off-by: Arthur Schiwon --- apps/workflowengine/lib/Manager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index 3f01b890a52..a0dda9cb6bc 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -129,6 +129,7 @@ class Manager implements IManager, IEntityAware { $this->storage = $storage; if ($storage->instanceOfStorage(Jail::class)) { + /** @var Jail $storage */ $path = $storage->getJailedPath($path); } $this->path = $path; From 5891ec602f45cebd7d991814c2ca57929f9085ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 6 Sep 2019 11:44:45 +0200 Subject: [PATCH 26/68] Return actual event data instead of self calling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- lib/public/WorkflowEngine/GenericEntityEvent.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/public/WorkflowEngine/GenericEntityEvent.php b/lib/public/WorkflowEngine/GenericEntityEvent.php index d72093573bf..f556eb93b22 100644 --- a/lib/public/WorkflowEngine/GenericEntityEvent.php +++ b/lib/public/WorkflowEngine/GenericEntityEvent.php @@ -51,7 +51,7 @@ class GenericEntityEvent implements IEntityEvent { * @since 18.0.0 */ public function getDisplayName(): string { - return $this->getDisplayName(); + return $this->displayName; } /** @@ -62,6 +62,6 @@ class GenericEntityEvent implements IEntityEvent { * @since 18.0.0 */ public function getEventName(): string { - return $this->getEventName(); + return $this->eventName; } } From 32279ed062b92e58c45cc87d0ecab41075cad3dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 6 Sep 2019 13:59:41 +0200 Subject: [PATCH 27/68] Extend missing check classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- .../lib/Check/RequestRemoteAddress.php | 28 +++++++++++++++++++ apps/workflowengine/lib/Check/RequestTime.php | 13 +++++++++ .../lib/Check/RequestUserAgent.php | 2 ++ .../lib/Check/UserGroupMembership.php | 11 ++++++++ 4 files changed, 54 insertions(+) diff --git a/apps/workflowengine/lib/Check/RequestRemoteAddress.php b/apps/workflowengine/lib/Check/RequestRemoteAddress.php index 6fa4cfc8800..c386168f597 100644 --- a/apps/workflowengine/lib/Check/RequestRemoteAddress.php +++ b/apps/workflowengine/lib/Check/RequestRemoteAddress.php @@ -151,4 +151,32 @@ class RequestRemoteAddress implements ICheck { } return str_pad($binaryIp, 128, '0', STR_PAD_RIGHT); } + + /** + * returns a list of Entities the checker supports. The values must match + * the class name of the entity. + * + * An empty result means the check is universally available. + * + * @since 18.0.0 + */ + public function supportedEntities(): array { + return []; + } + + /** + * returns whether the operation can be used in the requested scope. + * + * Scope IDs are defined as constants in OCP\WorkflowEngine\IManager. At + * time of writing these are SCOPE_ADMIN and SCOPE_USER. + * + * For possibly unknown future scopes the recommended behaviour is: if + * user scope is permitted, the default behaviour should return `true`, + * otherwise `false`. + * + * @since 18.0.0 + */ + public function isAvailableForScope(int $scope): bool { + return true; + } } diff --git a/apps/workflowengine/lib/Check/RequestTime.php b/apps/workflowengine/lib/Check/RequestTime.php index 0f5322b4759..79696583cdb 100644 --- a/apps/workflowengine/lib/Check/RequestTime.php +++ b/apps/workflowengine/lib/Check/RequestTime.php @@ -130,4 +130,17 @@ class RequestTime implements ICheck { public function isAvailableForScope(int $scope): bool { return true; } + + /** + * returns a list of Entities the checker supports. The values must match + * the class name of the entity. + * + * An empty result means the check is universally available. + * + * @since 18.0.0 + */ + public function supportedEntities(): array { + return []; + } + } diff --git a/apps/workflowengine/lib/Check/RequestUserAgent.php b/apps/workflowengine/lib/Check/RequestUserAgent.php index 2b2e3015557..1abb56bbe2d 100644 --- a/apps/workflowengine/lib/Check/RequestUserAgent.php +++ b/apps/workflowengine/lib/Check/RequestUserAgent.php @@ -24,6 +24,7 @@ namespace OCA\WorkflowEngine\Check; use OCP\IL10N; use OCP\IRequest; +use OCP\WorkflowEngine\IManager; class RequestUserAgent extends AbstractStringCheck { @@ -83,4 +84,5 @@ class RequestUserAgent extends AbstractStringCheck { public function isAvailableForScope(int $scope): bool { return true; } + } diff --git a/apps/workflowengine/lib/Check/UserGroupMembership.php b/apps/workflowengine/lib/Check/UserGroupMembership.php index fd6ba00d092..4b06dbd2da4 100644 --- a/apps/workflowengine/lib/Check/UserGroupMembership.php +++ b/apps/workflowengine/lib/Check/UserGroupMembership.php @@ -28,6 +28,7 @@ use OCP\IL10N; use OCP\IUser; use OCP\IUserSession; use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IManager; class UserGroupMembership implements ICheck { @@ -111,4 +112,14 @@ class UserGroupMembership implements ICheck { return $this->cachedGroupMemberships; } + + public function supportedEntities(): array { + // universal by default + return []; + } + + public function isAvailableForScope(int $scope): bool { + // admin only by default + return $scope === IManager::SCOPE_ADMIN; + } } From 687edb4bc80cc5cda3663997d23a319d7ea15925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 6 Sep 2019 14:08:01 +0200 Subject: [PATCH 28/68] No need to get the class since the entity is already the class string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/workflowengine/lib/Manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index a0dda9cb6bc..2e78b75aeb8 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -523,7 +523,7 @@ class Manager implements IManager, IEntityAware { } if (!empty($instance->supportedEntities()) - && !in_array(get_class($entity), $instance->supportedEntities()) + && !in_array($entity, $instance->supportedEntities()) ) { throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class])); } From fd2de58503a3c915cf178385db3e9962845719c4 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 9 Sep 2019 14:27:10 +0200 Subject: [PATCH 29/68] seperate setFileInfo from ICheck Signed-off-by: Arthur Schiwon --- .../lib/Check/AbstractStringCheck.php | 8 ---- .../workflowengine/lib/Check/FileMimeType.php | 3 +- apps/workflowengine/lib/Check/FileName.php | 3 +- apps/workflowengine/lib/Check/FileSize.php | 7 --- .../lib/Check/FileSystemTags.php | 3 +- .../lib/Check/RequestRemoteAddress.php | 8 ---- apps/workflowengine/lib/Check/RequestTime.php | 8 ---- .../lib/Check/UserGroupMembership.php | 8 ---- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/public/WorkflowEngine/ICheck.php | 11 +---- lib/public/WorkflowEngine/IFileCheck.php | 44 +++++++++++++++++++ 12 files changed, 53 insertions(+), 52 deletions(-) create mode 100644 lib/public/WorkflowEngine/IFileCheck.php diff --git a/apps/workflowengine/lib/Check/AbstractStringCheck.php b/apps/workflowengine/lib/Check/AbstractStringCheck.php index 7a385729e09..edd983ba699 100644 --- a/apps/workflowengine/lib/Check/AbstractStringCheck.php +++ b/apps/workflowengine/lib/Check/AbstractStringCheck.php @@ -42,14 +42,6 @@ abstract class AbstractStringCheck implements ICheck { $this->l = $l; } - /** - * @param IStorage $storage - * @param string $path - */ - public function setFileInfo(IStorage $storage, $path) { - // Nothing changes here with a different path - } - /** * @return string */ diff --git a/apps/workflowengine/lib/Check/FileMimeType.php b/apps/workflowengine/lib/Check/FileMimeType.php index 6d51992fdd0..52fc54fc859 100644 --- a/apps/workflowengine/lib/Check/FileMimeType.php +++ b/apps/workflowengine/lib/Check/FileMimeType.php @@ -27,8 +27,9 @@ use OCP\Files\IMimeTypeDetector; use OCP\Files\Storage\IStorage; use OCP\IL10N; use OCP\IRequest; +use OCP\WorkflowEngine\IFileCheck; -class FileMimeType extends AbstractStringCheck { +class FileMimeType extends AbstractStringCheck implements IFileCheck { /** @var array */ protected $mimeType; diff --git a/apps/workflowengine/lib/Check/FileName.php b/apps/workflowengine/lib/Check/FileName.php index d9b63ebfed1..76c48b7955e 100644 --- a/apps/workflowengine/lib/Check/FileName.php +++ b/apps/workflowengine/lib/Check/FileName.php @@ -26,8 +26,9 @@ use OCA\WorkflowEngine\Entity\File; use OCP\Files\Storage\IStorage; use OCP\IL10N; use OCP\IRequest; +use OCP\WorkflowEngine\IFileCheck; -class FileName extends AbstractStringCheck { +class FileName extends AbstractStringCheck implements IFileCheck { /** @var IRequest */ protected $request; diff --git a/apps/workflowengine/lib/Check/FileSize.php b/apps/workflowengine/lib/Check/FileSize.php index 06a45bdcd41..ac1896c6057 100644 --- a/apps/workflowengine/lib/Check/FileSize.php +++ b/apps/workflowengine/lib/Check/FileSize.php @@ -49,13 +49,6 @@ class FileSize implements ICheck { $this->request = $request; } - /** - * @param IStorage $storage - * @param string $path - */ - public function setFileInfo(IStorage $storage, $path) { - } - /** * @param string $operator * @param string $value diff --git a/apps/workflowengine/lib/Check/FileSystemTags.php b/apps/workflowengine/lib/Check/FileSystemTags.php index 12285c9b33e..41b657d53b0 100644 --- a/apps/workflowengine/lib/Check/FileSystemTags.php +++ b/apps/workflowengine/lib/Check/FileSystemTags.php @@ -31,8 +31,9 @@ use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\SystemTag\TagNotFoundException; use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IFileCheck; -class FileSystemTags implements ICheck { +class FileSystemTags implements ICheck, IFileCheck { /** @var array */ protected $fileIds; diff --git a/apps/workflowengine/lib/Check/RequestRemoteAddress.php b/apps/workflowengine/lib/Check/RequestRemoteAddress.php index c386168f597..28e3bd4be09 100644 --- a/apps/workflowengine/lib/Check/RequestRemoteAddress.php +++ b/apps/workflowengine/lib/Check/RequestRemoteAddress.php @@ -44,14 +44,6 @@ class RequestRemoteAddress implements ICheck { $this->request = $request; } - /** - * @param IStorage $storage - * @param string $path - */ - public function setFileInfo(IStorage $storage, $path) { - // A different path doesn't change time, so nothing to do here. - } - /** * @param string $operator * @param string $value diff --git a/apps/workflowengine/lib/Check/RequestTime.php b/apps/workflowengine/lib/Check/RequestTime.php index 79696583cdb..523d251789a 100644 --- a/apps/workflowengine/lib/Check/RequestTime.php +++ b/apps/workflowengine/lib/Check/RequestTime.php @@ -49,14 +49,6 @@ class RequestTime implements ICheck { $this->timeFactory = $timeFactory; } - /** - * @param IStorage $storage - * @param string $path - */ - public function setFileInfo(IStorage $storage, $path) { - // A different path doesn't change time, so nothing to do here. - } - /** * @param string $operator * @param string $value diff --git a/apps/workflowengine/lib/Check/UserGroupMembership.php b/apps/workflowengine/lib/Check/UserGroupMembership.php index 4b06dbd2da4..e9518733f9b 100644 --- a/apps/workflowengine/lib/Check/UserGroupMembership.php +++ b/apps/workflowengine/lib/Check/UserGroupMembership.php @@ -58,14 +58,6 @@ class UserGroupMembership implements ICheck { $this->l = $l; } - /** - * @param IStorage $storage - * @param string $path - */ - public function setFileInfo(IStorage $storage, $path) { - // A different path doesn't change group memberships, so nothing to do here. - } - /** * @param string $operator * @param string $value diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 6673cbe70a7..4078193b18a 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -444,6 +444,7 @@ return array( 'OCP\\WorkflowEngine\\IEntity' => $baseDir . '/lib/public/WorkflowEngine/IEntity.php', 'OCP\\WorkflowEngine\\IEntityAware' => $baseDir . '/lib/public/WorkflowEngine/IEntityAware.php', 'OCP\\WorkflowEngine\\IEntityEvent' => $baseDir . '/lib/public/WorkflowEngine/IEntityEvent.php', + 'OCP\\WorkflowEngine\\IFileCheck' => $baseDir . '/lib/public/WorkflowEngine/IFileCheck.php', 'OCP\\WorkflowEngine\\IManager' => $baseDir . '/lib/public/WorkflowEngine/IManager.php', 'OCP\\WorkflowEngine\\IOperation' => $baseDir . '/lib/public/WorkflowEngine/IOperation.php', 'OCP\\WorkflowEngine\\ISpecificOperation' => $baseDir . '/lib/public/WorkflowEngine/ISpecificOperation.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index d4b74d5c231..4cf093dcaa8 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -478,6 +478,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\WorkflowEngine\\IEntity' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntity.php', 'OCP\\WorkflowEngine\\IEntityAware' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityAware.php', 'OCP\\WorkflowEngine\\IEntityEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityEvent.php', + 'OCP\\WorkflowEngine\\IFileCheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IFileCheck.php', 'OCP\\WorkflowEngine\\IManager' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IManager.php', 'OCP\\WorkflowEngine\\IOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IOperation.php', 'OCP\\WorkflowEngine\\ISpecificOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ISpecificOperation.php', diff --git a/lib/public/WorkflowEngine/ICheck.php b/lib/public/WorkflowEngine/ICheck.php index 92ec6f83893..f5586e83d51 100644 --- a/lib/public/WorkflowEngine/ICheck.php +++ b/lib/public/WorkflowEngine/ICheck.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2016 Morris Jobke * + * @author Arthur Schiwon * @author Morris Jobke * * @license GNU AGPL version 3 or any later version @@ -23,9 +24,6 @@ namespace OCP\WorkflowEngine; - -use OCP\Files\Storage\IStorage; - /** * Interface ICheck * @@ -33,13 +31,6 @@ use OCP\Files\Storage\IStorage; * @since 9.1 */ interface ICheck { - /** - * @param IStorage $storage - * @param string $path - * @since 9.1 - */ - public function setFileInfo(IStorage $storage, $path); - /** * @param string $operator * @param string $value diff --git a/lib/public/WorkflowEngine/IFileCheck.php b/lib/public/WorkflowEngine/IFileCheck.php new file mode 100644 index 00000000000..ce15d6c352c --- /dev/null +++ b/lib/public/WorkflowEngine/IFileCheck.php @@ -0,0 +1,44 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\WorkflowEngine; + + +use OCP\Files\Storage\IStorage; + +/** + * Interface IFileCheck + * + * @package OCP\WorkflowEngine + * @since 18.0.0 + */ +interface IFileCheck { + /** + * @param IStorage $storage + * @param string $path + * @since 18.0.0 + */ + public function setFileInfo(IStorage $storage, $path); + +} From 4cd931fcc6bb7f794fd14ddb75c7867823449e79 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 9 Sep 2019 16:04:12 +0200 Subject: [PATCH 30/68] require IChecks to receive entity context Signed-off-by: Arthur Schiwon --- .../composer/composer/autoload_classmap.php | 1 + .../composer/composer/autoload_static.php | 1 + apps/workflowengine/lib/Check/AFileCheck.php | 67 +++++++++++++++++++ .../lib/Check/AbstractStringCheck.php | 6 +- .../workflowengine/lib/Check/FileMimeType.php | 12 ++-- apps/workflowengine/lib/Check/FileName.php | 17 +---- apps/workflowengine/lib/Check/FileSize.php | 5 ++ .../lib/Check/FileSystemTags.php | 17 +---- .../lib/Check/RequestRemoteAddress.php | 6 +- apps/workflowengine/lib/Check/RequestTime.php | 5 +- .../lib/Check/RequestUserAgent.php | 1 - .../lib/Check/UserGroupMembership.php | 6 +- lib/public/WorkflowEngine/ICheck.php | 20 ++++++ 13 files changed, 119 insertions(+), 45 deletions(-) create mode 100644 apps/workflowengine/lib/Check/AFileCheck.php diff --git a/apps/workflowengine/composer/composer/autoload_classmap.php b/apps/workflowengine/composer/composer/autoload_classmap.php index 4dab03642dd..6264358788c 100644 --- a/apps/workflowengine/composer/composer/autoload_classmap.php +++ b/apps/workflowengine/composer/composer/autoload_classmap.php @@ -16,6 +16,7 @@ return array( 'OCA\\WorkflowEngine\\Check\\RequestTime' => $baseDir . '/../lib/Check/RequestTime.php', 'OCA\\WorkflowEngine\\Check\\RequestURL' => $baseDir . '/../lib/Check/RequestURL.php', 'OCA\\WorkflowEngine\\Check\\RequestUserAgent' => $baseDir . '/../lib/Check/RequestUserAgent.php', + 'OCA\\WorkflowEngine\\Check\\TFileCheck' => $baseDir . '/../lib/Check/AFileCheck.php', 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => $baseDir . '/../lib/Check/UserGroupMembership.php', 'OCA\\WorkflowEngine\\Command\\Index' => $baseDir . '/../lib/Command/Index.php', 'OCA\\WorkflowEngine\\Controller\\AWorkflowController' => $baseDir . '/../lib/Controller/AWorkflowController.php', diff --git a/apps/workflowengine/composer/composer/autoload_static.php b/apps/workflowengine/composer/composer/autoload_static.php index 38b1a5b0e76..cd370986f15 100644 --- a/apps/workflowengine/composer/composer/autoload_static.php +++ b/apps/workflowengine/composer/composer/autoload_static.php @@ -31,6 +31,7 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Check\\RequestTime' => __DIR__ . '/..' . '/../lib/Check/RequestTime.php', 'OCA\\WorkflowEngine\\Check\\RequestURL' => __DIR__ . '/..' . '/../lib/Check/RequestURL.php', 'OCA\\WorkflowEngine\\Check\\RequestUserAgent' => __DIR__ . '/..' . '/../lib/Check/RequestUserAgent.php', + 'OCA\\WorkflowEngine\\Check\\TFileCheck' => __DIR__ . '/..' . '/../lib/Check/AFileCheck.php', 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => __DIR__ . '/..' . '/../lib/Check/UserGroupMembership.php', 'OCA\\WorkflowEngine\\Command\\Index' => __DIR__ . '/..' . '/../lib/Command/Index.php', 'OCA\\WorkflowEngine\\Controller\\AWorkflowController' => __DIR__ . '/..' . '/../lib/Controller/AWorkflowController.php', diff --git a/apps/workflowengine/lib/Check/AFileCheck.php b/apps/workflowengine/lib/Check/AFileCheck.php new file mode 100644 index 00000000000..45d251b1659 --- /dev/null +++ b/apps/workflowengine/lib/Check/AFileCheck.php @@ -0,0 +1,67 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Check; + +use OCA\WorkflowEngine\AppInfo\Application; +use OCA\WorkflowEngine\Entity\File; +use OCP\Files\Node; +use OCP\Files\Storage\IStorage; +use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IEntity; +use OCP\WorkflowEngine\IFileCheck; + +trait TFileCheck { + /** @var IStorage */ + protected $storage; + + /** @var string */ + protected $path; + + /** + * @param IStorage $storage + * @param string $path + * @since 18.0.0 + */ + public function setFileInfo(IStorage $storage, $path) { + $this->storage = $storage; + $this->path = $path; + } + + /** + * @throws \OCP\Files\NotFoundException + */ + public function setEntitySubject(IEntity $entity, $subject): void { + if ($entity instanceof File) { + if (!$subject instanceof Node) { + throw new \UnexpectedValueException( + 'Expected Node subject for File entity, got {class}', + ['app' => Application::APP_ID, 'class' => get_class($subject)] + ); + } + $this->storage = $subject->getStorage(); + $this->path = $subject->getPath(); + } + } +} diff --git a/apps/workflowengine/lib/Check/AbstractStringCheck.php b/apps/workflowengine/lib/Check/AbstractStringCheck.php index edd983ba699..ec9a1945d9c 100644 --- a/apps/workflowengine/lib/Check/AbstractStringCheck.php +++ b/apps/workflowengine/lib/Check/AbstractStringCheck.php @@ -22,9 +22,9 @@ namespace OCA\WorkflowEngine\Check; -use OCP\Files\Storage\IStorage; use OCP\IL10N; use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IManager; abstract class AbstractStringCheck implements ICheck { @@ -121,4 +121,8 @@ abstract class AbstractStringCheck implements ICheck { $this->matches[$patternHash][$subjectHash] = preg_match($pattern, $subject); return $this->matches[$patternHash][$subjectHash]; } + + public function setEntitySubject(IEntity $entity, $subject): void { + // Noop + } } diff --git a/apps/workflowengine/lib/Check/FileMimeType.php b/apps/workflowengine/lib/Check/FileMimeType.php index 52fc54fc859..92375d4d3cd 100644 --- a/apps/workflowengine/lib/Check/FileMimeType.php +++ b/apps/workflowengine/lib/Check/FileMimeType.php @@ -30,6 +30,9 @@ use OCP\IRequest; use OCP\WorkflowEngine\IFileCheck; class FileMimeType extends AbstractStringCheck implements IFileCheck { + use TFileCheck { + setFileInfo as _setFileInfo; + } /** @var array */ protected $mimeType; @@ -40,12 +43,6 @@ class FileMimeType extends AbstractStringCheck implements IFileCheck { /** @var IMimeTypeDetector */ protected $mimeTypeDetector; - /** @var IStorage */ - protected $storage; - - /** @var string */ - protected $path; - /** * @param IL10N $l * @param IRequest $request @@ -62,8 +59,7 @@ class FileMimeType extends AbstractStringCheck implements IFileCheck { * @param string $path */ public function setFileInfo(IStorage $storage, $path) { - $this->storage = $storage; - $this->path = $path; + $this->_setFileInfo($storage, $path); if (!isset($this->mimeType[$this->storage->getId()][$this->path]) || $this->mimeType[$this->storage->getId()][$this->path] === '') { $this->mimeType[$this->storage->getId()][$this->path] = null; diff --git a/apps/workflowengine/lib/Check/FileName.php b/apps/workflowengine/lib/Check/FileName.php index 76c48b7955e..62ee601980b 100644 --- a/apps/workflowengine/lib/Check/FileName.php +++ b/apps/workflowengine/lib/Check/FileName.php @@ -23,22 +23,16 @@ declare(strict_types=1); namespace OCA\WorkflowEngine\Check; use OCA\WorkflowEngine\Entity\File; -use OCP\Files\Storage\IStorage; use OCP\IL10N; use OCP\IRequest; use OCP\WorkflowEngine\IFileCheck; class FileName extends AbstractStringCheck implements IFileCheck { + use TFileCheck; /** @var IRequest */ protected $request; - /** @var IStorage */ - protected $storage; - - /** @var string */ - protected $path; - /** * @param IL10N $l * @param IRequest $request @@ -48,15 +42,6 @@ class FileName extends AbstractStringCheck implements IFileCheck { $this->request = $request; } - /** - * @param IStorage $storage - * @param string $path - */ - public function setFileInfo(IStorage $storage, $path) { - $this->storage = $storage; - $this->path = $path; - } - /** * @return string */ diff --git a/apps/workflowengine/lib/Check/FileSize.php b/apps/workflowengine/lib/Check/FileSize.php index ac1896c6057..2ad4bb09e01 100644 --- a/apps/workflowengine/lib/Check/FileSize.php +++ b/apps/workflowengine/lib/Check/FileSize.php @@ -28,6 +28,7 @@ use OCP\IL10N; use OCP\IRequest; use OCP\Util; use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IEntity; class FileSize implements ICheck { @@ -118,4 +119,8 @@ class FileSize implements ICheck { public function isAvailableForScope(int $scope): bool { return true; } + + public function setEntitySubject(IEntity $entity, $subject): void { + // NOOP + } } diff --git a/apps/workflowengine/lib/Check/FileSystemTags.php b/apps/workflowengine/lib/Check/FileSystemTags.php index 41b657d53b0..e7e114881ad 100644 --- a/apps/workflowengine/lib/Check/FileSystemTags.php +++ b/apps/workflowengine/lib/Check/FileSystemTags.php @@ -25,7 +25,6 @@ namespace OCA\WorkflowEngine\Check; use OCA\WorkflowEngine\Entity\File; use OCP\Files\Cache\ICache; use OCP\Files\IHomeStorage; -use OCP\Files\Storage\IStorage; use OCP\IL10N; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; @@ -34,6 +33,7 @@ use OCP\WorkflowEngine\ICheck; use OCP\WorkflowEngine\IFileCheck; class FileSystemTags implements ICheck, IFileCheck { + use TFileCheck; /** @var array */ protected $fileIds; @@ -50,12 +50,6 @@ class FileSystemTags implements ICheck, IFileCheck { /** @var ISystemTagObjectMapper */ protected $systemTagObjectMapper; - /** @var IStorage */ - protected $storage; - - /** @var string */ - protected $path; - /** * @param IL10N $l * @param ISystemTagManager $systemTagManager @@ -67,15 +61,6 @@ class FileSystemTags implements ICheck, IFileCheck { $this->systemTagObjectMapper = $systemTagObjectMapper; } - /** - * @param IStorage $storage - * @param string $path - */ - public function setFileInfo(IStorage $storage, $path) { - $this->storage = $storage; - $this->path = $path; - } - /** * @param string $operator * @param string $value diff --git a/apps/workflowengine/lib/Check/RequestRemoteAddress.php b/apps/workflowengine/lib/Check/RequestRemoteAddress.php index 28e3bd4be09..0079db4b831 100644 --- a/apps/workflowengine/lib/Check/RequestRemoteAddress.php +++ b/apps/workflowengine/lib/Check/RequestRemoteAddress.php @@ -22,10 +22,10 @@ namespace OCA\WorkflowEngine\Check; -use OCP\Files\Storage\IStorage; use OCP\IL10N; use OCP\IRequest; use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IEntity; class RequestRemoteAddress implements ICheck { @@ -171,4 +171,8 @@ class RequestRemoteAddress implements ICheck { public function isAvailableForScope(int $scope): bool { return true; } + + public function setEntitySubject(IEntity $entity, $subject): void { + // NOOP + } } diff --git a/apps/workflowengine/lib/Check/RequestTime.php b/apps/workflowengine/lib/Check/RequestTime.php index 523d251789a..bf37bf3d2ba 100644 --- a/apps/workflowengine/lib/Check/RequestTime.php +++ b/apps/workflowengine/lib/Check/RequestTime.php @@ -23,9 +23,9 @@ namespace OCA\WorkflowEngine\Check; use OCP\AppFramework\Utility\ITimeFactory; -use OCP\Files\Storage\IStorage; use OCP\IL10N; use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IEntity; class RequestTime implements ICheck { @@ -135,4 +135,7 @@ class RequestTime implements ICheck { return []; } + public function setEntitySubject(IEntity $entity, $subject): void { + // NOOP + } } diff --git a/apps/workflowengine/lib/Check/RequestUserAgent.php b/apps/workflowengine/lib/Check/RequestUserAgent.php index 1abb56bbe2d..ddc222833dd 100644 --- a/apps/workflowengine/lib/Check/RequestUserAgent.php +++ b/apps/workflowengine/lib/Check/RequestUserAgent.php @@ -24,7 +24,6 @@ namespace OCA\WorkflowEngine\Check; use OCP\IL10N; use OCP\IRequest; -use OCP\WorkflowEngine\IManager; class RequestUserAgent extends AbstractStringCheck { diff --git a/apps/workflowengine/lib/Check/UserGroupMembership.php b/apps/workflowengine/lib/Check/UserGroupMembership.php index e9518733f9b..55f1d017ed0 100644 --- a/apps/workflowengine/lib/Check/UserGroupMembership.php +++ b/apps/workflowengine/lib/Check/UserGroupMembership.php @@ -22,12 +22,12 @@ namespace OCA\WorkflowEngine\Check; -use OCP\Files\Storage\IStorage; use OCP\IGroupManager; use OCP\IL10N; use OCP\IUser; use OCP\IUserSession; use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IManager; class UserGroupMembership implements ICheck { @@ -114,4 +114,8 @@ class UserGroupMembership implements ICheck { // admin only by default return $scope === IManager::SCOPE_ADMIN; } + + public function setEntitySubject(IEntity $entity, $subject): void { + // NOOP + } } diff --git a/lib/public/WorkflowEngine/ICheck.php b/lib/public/WorkflowEngine/ICheck.php index f5586e83d51..6e9c2fc765e 100644 --- a/lib/public/WorkflowEngine/ICheck.php +++ b/lib/public/WorkflowEngine/ICheck.php @@ -70,4 +70,24 @@ interface ICheck { * @since 18.0.0 */ public function isAvailableForScope(int $scope): bool; + + /** + * Equips the check with a subject fitting the Entity. For instance, an + * entity of File will receive an instance of OCP\Files\Node, or a comment + * entity might get an IComment. + * + * The implementing check must be aware of the incoming type. + * + * If an unsupported subject is passed the implementation MAY throw an + * \UnexpectedValueException. + * + * When an implementation does not depend on a subject being passed to it, + * for example RequestTime, the implemented method SHOULD just pass, without + * any further operation. + * + * @param IEntity $entity + * @param mixed $subject + * @throws \UnexpectedValueException + */ + public function setEntitySubject(IEntity $entity, $subject): void; } From 849d025d093d1c80d3820901d20bf6a664c6af02 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 9 Sep 2019 16:53:59 +0200 Subject: [PATCH 31/68] let a dedicate service serve a stateful process * includes making ICheck not requiring any context setter * and IFileCheck extending the IEntityCheck as entity data can be handed in via Dispatcher Signed-off-by: Arthur Schiwon --- .../composer/composer/autoload_classmap.php | 3 +- .../composer/composer/autoload_static.php | 3 +- .../lib/Check/AbstractStringCheck.php | 5 - apps/workflowengine/lib/Check/FileSize.php | 5 - .../lib/Check/RequestRemoteAddress.php | 5 - apps/workflowengine/lib/Check/RequestTime.php | 5 - .../Check/{AFileCheck.php => TFileCheck.php} | 2 - .../lib/Check/UserGroupMembership.php | 5 - apps/workflowengine/lib/Manager.php | 97 +------------ .../lib/Service/RuleMatcher.php | 137 ++++++++++++++++++ lib/composer/composer/autoload_classmap.php | 3 +- lib/composer/composer/autoload_static.php | 3 +- lib/public/WorkflowEngine/ICheck.php | 20 --- lib/public/WorkflowEngine/IEntityCheck.php | 53 +++++++ lib/public/WorkflowEngine/IFileCheck.php | 6 +- lib/public/WorkflowEngine/IManager.php | 23 +-- .../{IEntityAware.php => IRuleMatcher.php} | 7 +- 17 files changed, 213 insertions(+), 169 deletions(-) rename apps/workflowengine/lib/Check/{AFileCheck.php => TFileCheck.php} (96%) create mode 100644 apps/workflowengine/lib/Service/RuleMatcher.php create mode 100644 lib/public/WorkflowEngine/IEntityCheck.php rename lib/public/WorkflowEngine/{IEntityAware.php => IRuleMatcher.php} (85%) diff --git a/apps/workflowengine/composer/composer/autoload_classmap.php b/apps/workflowengine/composer/composer/autoload_classmap.php index 6264358788c..0de2a50234c 100644 --- a/apps/workflowengine/composer/composer/autoload_classmap.php +++ b/apps/workflowengine/composer/composer/autoload_classmap.php @@ -16,7 +16,7 @@ return array( 'OCA\\WorkflowEngine\\Check\\RequestTime' => $baseDir . '/../lib/Check/RequestTime.php', 'OCA\\WorkflowEngine\\Check\\RequestURL' => $baseDir . '/../lib/Check/RequestURL.php', 'OCA\\WorkflowEngine\\Check\\RequestUserAgent' => $baseDir . '/../lib/Check/RequestUserAgent.php', - 'OCA\\WorkflowEngine\\Check\\TFileCheck' => $baseDir . '/../lib/Check/AFileCheck.php', + 'OCA\\WorkflowEngine\\Check\\TFileCheck' => $baseDir . '/../lib/Check/TFileCheck.php', 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => $baseDir . '/../lib/Check/UserGroupMembership.php', 'OCA\\WorkflowEngine\\Command\\Index' => $baseDir . '/../lib/Command/Index.php', 'OCA\\WorkflowEngine\\Controller\\AWorkflowController' => $baseDir . '/../lib/Controller/AWorkflowController.php', @@ -28,6 +28,7 @@ return array( 'OCA\\WorkflowEngine\\Manager' => $baseDir . '/../lib/Manager.php', 'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => $baseDir . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php', 'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => $baseDir . '/../lib/Migration/Version2019Date20190808074233.php', + 'OCA\\WorkflowEngine\\Service\\RuleMatcher' => $baseDir . '/../lib/Service/RuleMatcher.php', 'OCA\\WorkflowEngine\\Settings\\ASettings' => $baseDir . '/../lib/Settings/ASettings.php', 'OCA\\WorkflowEngine\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php', 'OCA\\WorkflowEngine\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php', diff --git a/apps/workflowengine/composer/composer/autoload_static.php b/apps/workflowengine/composer/composer/autoload_static.php index cd370986f15..0f8a488295b 100644 --- a/apps/workflowengine/composer/composer/autoload_static.php +++ b/apps/workflowengine/composer/composer/autoload_static.php @@ -31,7 +31,7 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Check\\RequestTime' => __DIR__ . '/..' . '/../lib/Check/RequestTime.php', 'OCA\\WorkflowEngine\\Check\\RequestURL' => __DIR__ . '/..' . '/../lib/Check/RequestURL.php', 'OCA\\WorkflowEngine\\Check\\RequestUserAgent' => __DIR__ . '/..' . '/../lib/Check/RequestUserAgent.php', - 'OCA\\WorkflowEngine\\Check\\TFileCheck' => __DIR__ . '/..' . '/../lib/Check/AFileCheck.php', + 'OCA\\WorkflowEngine\\Check\\TFileCheck' => __DIR__ . '/..' . '/../lib/Check/TFileCheck.php', 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => __DIR__ . '/..' . '/../lib/Check/UserGroupMembership.php', 'OCA\\WorkflowEngine\\Command\\Index' => __DIR__ . '/..' . '/../lib/Command/Index.php', 'OCA\\WorkflowEngine\\Controller\\AWorkflowController' => __DIR__ . '/..' . '/../lib/Controller/AWorkflowController.php', @@ -43,6 +43,7 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\Manager' => __DIR__ . '/..' . '/../lib/Manager.php', 'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => __DIR__ . '/..' . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php', 'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => __DIR__ . '/..' . '/../lib/Migration/Version2019Date20190808074233.php', + 'OCA\\WorkflowEngine\\Service\\RuleMatcher' => __DIR__ . '/..' . '/../lib/Service/RuleMatcher.php', 'OCA\\WorkflowEngine\\Settings\\ASettings' => __DIR__ . '/..' . '/../lib/Settings/ASettings.php', 'OCA\\WorkflowEngine\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php', 'OCA\\WorkflowEngine\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php', diff --git a/apps/workflowengine/lib/Check/AbstractStringCheck.php b/apps/workflowengine/lib/Check/AbstractStringCheck.php index ec9a1945d9c..e86ac6bec17 100644 --- a/apps/workflowengine/lib/Check/AbstractStringCheck.php +++ b/apps/workflowengine/lib/Check/AbstractStringCheck.php @@ -24,7 +24,6 @@ namespace OCA\WorkflowEngine\Check; use OCP\IL10N; use OCP\WorkflowEngine\ICheck; -use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IManager; abstract class AbstractStringCheck implements ICheck { @@ -121,8 +120,4 @@ abstract class AbstractStringCheck implements ICheck { $this->matches[$patternHash][$subjectHash] = preg_match($pattern, $subject); return $this->matches[$patternHash][$subjectHash]; } - - public function setEntitySubject(IEntity $entity, $subject): void { - // Noop - } } diff --git a/apps/workflowengine/lib/Check/FileSize.php b/apps/workflowengine/lib/Check/FileSize.php index 2ad4bb09e01..736a3d932fb 100644 --- a/apps/workflowengine/lib/Check/FileSize.php +++ b/apps/workflowengine/lib/Check/FileSize.php @@ -23,7 +23,6 @@ namespace OCA\WorkflowEngine\Check; use OCA\WorkflowEngine\Entity\File; -use OCP\Files\Storage\IStorage; use OCP\IL10N; use OCP\IRequest; use OCP\Util; @@ -119,8 +118,4 @@ class FileSize implements ICheck { public function isAvailableForScope(int $scope): bool { return true; } - - public function setEntitySubject(IEntity $entity, $subject): void { - // NOOP - } } diff --git a/apps/workflowengine/lib/Check/RequestRemoteAddress.php b/apps/workflowengine/lib/Check/RequestRemoteAddress.php index 0079db4b831..00029771b52 100644 --- a/apps/workflowengine/lib/Check/RequestRemoteAddress.php +++ b/apps/workflowengine/lib/Check/RequestRemoteAddress.php @@ -25,7 +25,6 @@ namespace OCA\WorkflowEngine\Check; use OCP\IL10N; use OCP\IRequest; use OCP\WorkflowEngine\ICheck; -use OCP\WorkflowEngine\IEntity; class RequestRemoteAddress implements ICheck { @@ -171,8 +170,4 @@ class RequestRemoteAddress implements ICheck { public function isAvailableForScope(int $scope): bool { return true; } - - public function setEntitySubject(IEntity $entity, $subject): void { - // NOOP - } } diff --git a/apps/workflowengine/lib/Check/RequestTime.php b/apps/workflowengine/lib/Check/RequestTime.php index bf37bf3d2ba..a6ab4a961ed 100644 --- a/apps/workflowengine/lib/Check/RequestTime.php +++ b/apps/workflowengine/lib/Check/RequestTime.php @@ -25,7 +25,6 @@ namespace OCA\WorkflowEngine\Check; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IL10N; use OCP\WorkflowEngine\ICheck; -use OCP\WorkflowEngine\IEntity; class RequestTime implements ICheck { @@ -134,8 +133,4 @@ class RequestTime implements ICheck { public function supportedEntities(): array { return []; } - - public function setEntitySubject(IEntity $entity, $subject): void { - // NOOP - } } diff --git a/apps/workflowengine/lib/Check/AFileCheck.php b/apps/workflowengine/lib/Check/TFileCheck.php similarity index 96% rename from apps/workflowengine/lib/Check/AFileCheck.php rename to apps/workflowengine/lib/Check/TFileCheck.php index 45d251b1659..44a39aadfde 100644 --- a/apps/workflowengine/lib/Check/AFileCheck.php +++ b/apps/workflowengine/lib/Check/TFileCheck.php @@ -28,9 +28,7 @@ use OCA\WorkflowEngine\AppInfo\Application; use OCA\WorkflowEngine\Entity\File; use OCP\Files\Node; use OCP\Files\Storage\IStorage; -use OCP\WorkflowEngine\ICheck; use OCP\WorkflowEngine\IEntity; -use OCP\WorkflowEngine\IFileCheck; trait TFileCheck { /** @var IStorage */ diff --git a/apps/workflowengine/lib/Check/UserGroupMembership.php b/apps/workflowengine/lib/Check/UserGroupMembership.php index 55f1d017ed0..8839820050a 100644 --- a/apps/workflowengine/lib/Check/UserGroupMembership.php +++ b/apps/workflowengine/lib/Check/UserGroupMembership.php @@ -27,7 +27,6 @@ use OCP\IL10N; use OCP\IUser; use OCP\IUserSession; use OCP\WorkflowEngine\ICheck; -use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IManager; class UserGroupMembership implements ICheck { @@ -114,8 +113,4 @@ class UserGroupMembership implements ICheck { // admin only by default return $scope === IManager::SCOPE_ADMIN; } - - public function setEntitySubject(IEntity $entity, $subject): void { - // NOOP - } } diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index 2e78b75aeb8..efe6c387059 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -21,7 +21,6 @@ namespace OCA\WorkflowEngine; -use OC\Files\Storage\Wrapper\Jail; use Doctrine\DBAL\DBALException; use OC\Cache\CappedMemoryCache; use OCA\WorkflowEngine\Check\FileMimeType; @@ -35,6 +34,7 @@ use OCA\WorkflowEngine\Check\RequestUserAgent; use OCA\WorkflowEngine\Check\UserGroupMembership; use OCA\WorkflowEngine\Entity\File; use OCA\WorkflowEngine\Helper\ScopeContext; +use OCA\WorkflowEngine\Service\RuleMatcher; use OCP\AppFramework\QueryException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Storage\IStorage; @@ -46,14 +46,14 @@ use OCP\IUserSession; use OCP\WorkflowEngine\ICheck; use OCP\WorkflowEngine\IComplexOperation; use OCP\WorkflowEngine\IEntity; -use OCP\WorkflowEngine\IEntityAware; use OCP\WorkflowEngine\IEntityEvent; use OCP\WorkflowEngine\IManager; use OCP\WorkflowEngine\IOperation; +use OCP\WorkflowEngine\IRuleMatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; -class Manager implements IManager, IEntityAware { +class Manager implements IManager { /** @var IStorage */ protected $storage; @@ -122,77 +122,8 @@ class Manager implements IManager, IEntityAware { $this->session = $session; } - /** - * @inheritdoc - */ - public function setFileInfo(IStorage $storage, $path) { - $this->storage = $storage; - - if ($storage->instanceOfStorage(Jail::class)) { - /** @var Jail $storage */ - $path = $storage->getJailedPath($path); - } - $this->path = $path; - } - - /** - * @inheritdoc - */ - public function getMatchingOperations($class, $returnFirstMatchingOperationOnly = true) { - $scopes[] = new ScopeContext(IManager::SCOPE_ADMIN); - $user = $this->session->getUser(); - if($user !== null) { - $scopes[] = new ScopeContext(IManager::SCOPE_USER, $user->getUID()); - } - - $operations = []; - foreach ($scopes as $scope) { - $operations = array_merge($operations, $this->getOperations($class, $scope)); - } - - $matches = []; - foreach ($operations as $operation) { - $checkIds = json_decode($operation['checks'], true); - $checks = $this->getChecks($checkIds); - - foreach ($checks as $check) { - if (!$this->check($check)) { - // Check did not match, continue with the next operation - continue 2; - } - } - - if ($returnFirstMatchingOperationOnly) { - return $operation; - } - $matches[] = $operation; - } - - return $matches; - } - - /** - * @param array $check - * @return bool - */ - protected function check(array $check) { - try { - $checkInstance = $this->container->query($check['class']); - } catch (QueryException $e) { - // Check does not exist, assume it matches. - return true; - } - - if ($checkInstance instanceof IEntityAware && $this->entity !== null) { - $checkInstance->setEntity($this->entity); - return $checkInstance->executeCheck($check['operator'], $check['value']); - } elseif ($checkInstance instanceof ICheck) { - $checkInstance->setFileInfo($this->storage, $this->path); - return $checkInstance->executeCheck($check['operator'], $check['value']); - } else { - // Check is invalid - throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class'])); - } + public function getRuleMatcher(): IRuleMatcher { + return new RuleMatcher($this->session, $this->container, $this->l, $this); } public function getAllConfiguredEvents() { @@ -634,24 +565,6 @@ class Manager implements IManager, IEntityAware { return $operation; } - /** - * @param object $entity - * @since 18.0.0 - */ - public function setEntity($entity) { - if(!is_object($entity)) { - $this->container->getLogger()->logException( - new \InvalidArgumentException('provided entity is not an object'), - [ - 'app' => 'workflowengine', - 'level' => ILogger::ERROR, - ] - ); - return; - } - $this->entity = $entity; - } - /** * @return IEntity[] */ diff --git a/apps/workflowengine/lib/Service/RuleMatcher.php b/apps/workflowengine/lib/Service/RuleMatcher.php new file mode 100644 index 00000000000..bc92eeee818 --- /dev/null +++ b/apps/workflowengine/lib/Service/RuleMatcher.php @@ -0,0 +1,137 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowEngine\Service; + +use OCA\WorkflowEngine\AppInfo\Application; +use OCA\WorkflowEngine\Entity\File; +use OCA\WorkflowEngine\Helper\ScopeContext; +use OCA\WorkflowEngine\Manager; +use OCP\AppFramework\QueryException; +use OCP\Files\Node; +use OCP\Files\Storage\IStorage; +use OCP\IL10N; +use OCP\IServerContainer; +use OCP\IUserSession; +use OCP\WorkflowEngine\ICheck; +use OCP\WorkflowEngine\IEntity; +use OCP\WorkflowEngine\IFileCheck; +use OCP\WorkflowEngine\IManager; +use OCP\WorkflowEngine\IRuleMatcher; + +class RuleMatcher implements IRuleMatcher { + + /** @var IUserSession */ + protected $session; + /** @var IManager */ + protected $manager; + /** @var array */ + protected $contexts; + /** @var IServerContainer */ + protected $container; + /** @var array */ + protected $fileInfo = []; + /** @var IL10N */ + protected $l; + + public function __construct(IUserSession $session, IServerContainer $container, IL10N $l, Manager $manager) { + $this->session = $session; + $this->manager = $manager; + $this->container = $container; + $this->l = $l; + } + + public function setFileInfo(IStorage $storage, string $path): void { + $this->fileInfo['storage'] = $storage; + $this->fileInfo['path'] = $path; + } + + + public function setEntitySubject(IEntity $entity, $subject): void { + $this->contexts[get_class($entity)] = [$entity, $subject]; + } + + public function getMatchingOperations(string $class, bool $returnFirstMatchingOperationOnly = true): array { + $scopes[] = new ScopeContext(IManager::SCOPE_ADMIN); + $user = $this->session->getUser(); + if($user !== null) { + $scopes[] = new ScopeContext(IManager::SCOPE_USER, $user->getUID()); + } + + $operations = []; + foreach ($scopes as $scope) { + $operations = array_merge($operations, $this->manager->getOperations($class, $scope)); + } + + $matches = []; + foreach ($operations as $operation) { + $checkIds = json_decode($operation['checks'], true); + $checks = $this->manager->getChecks($checkIds); + + foreach ($checks as $check) { + if (!$this->check($check)) { + // Check did not match, continue with the next operation + continue 2; + } + } + + if ($returnFirstMatchingOperationOnly) { + return $operation; + } + $matches[] = $operation; + } + + return $matches; + } + + /** + * @param array $check + * @return bool + */ + public function check(array $check) { + try { + $checkInstance = $this->container->query($check['class']); + } catch (QueryException $e) { + // Check does not exist, assume it matches. + return true; + } + + if ($checkInstance instanceof ICheck) { + foreach($this->contexts as $entityInfo) { + list($entity, $subject) = $entityInfo; + $checkInstance->setEntitySubject($entity, $subject); + } + return $checkInstance->executeCheck($check['operator'], $check['value']); + } elseif ($checkInstance instanceof IFileCheck) { + if(empty($this->fileInfo)) { + throw new \RuntimeException('Must set file info before running the check'); + } + $checkInstance->setFileInfo($this->fileInfo['storage'], $this->fileInfo['path']); + return $checkInstance->executeCheck($check['operator'], $check['value']); + } else { + // Check is invalid + throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class'])); + } + } +} diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 4078193b18a..b51b689b629 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -442,11 +442,12 @@ return array( 'OCP\\WorkflowEngine\\ICheck' => $baseDir . '/lib/public/WorkflowEngine/ICheck.php', 'OCP\\WorkflowEngine\\IComplexOperation' => $baseDir . '/lib/public/WorkflowEngine/IComplexOperation.php', 'OCP\\WorkflowEngine\\IEntity' => $baseDir . '/lib/public/WorkflowEngine/IEntity.php', - 'OCP\\WorkflowEngine\\IEntityAware' => $baseDir . '/lib/public/WorkflowEngine/IEntityAware.php', + 'OCP\\WorkflowEngine\\IEntityCheck' => $baseDir . '/lib/public/WorkflowEngine/IEntityCheck.php', 'OCP\\WorkflowEngine\\IEntityEvent' => $baseDir . '/lib/public/WorkflowEngine/IEntityEvent.php', 'OCP\\WorkflowEngine\\IFileCheck' => $baseDir . '/lib/public/WorkflowEngine/IFileCheck.php', 'OCP\\WorkflowEngine\\IManager' => $baseDir . '/lib/public/WorkflowEngine/IManager.php', 'OCP\\WorkflowEngine\\IOperation' => $baseDir . '/lib/public/WorkflowEngine/IOperation.php', + 'OCP\\WorkflowEngine\\IRuleMatcher' => $baseDir . '/lib/public/WorkflowEngine/IRuleMatcher.php', 'OCP\\WorkflowEngine\\ISpecificOperation' => $baseDir . '/lib/public/WorkflowEngine/ISpecificOperation.php', 'OC\\Accounts\\Account' => $baseDir . '/lib/private/Accounts/Account.php', 'OC\\Accounts\\AccountManager' => $baseDir . '/lib/private/Accounts/AccountManager.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 4cf093dcaa8..1bcefc1dc1c 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -476,11 +476,12 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\WorkflowEngine\\ICheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ICheck.php', 'OCP\\WorkflowEngine\\IComplexOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IComplexOperation.php', 'OCP\\WorkflowEngine\\IEntity' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntity.php', - 'OCP\\WorkflowEngine\\IEntityAware' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityAware.php', + 'OCP\\WorkflowEngine\\IEntityCheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityCheck.php', 'OCP\\WorkflowEngine\\IEntityEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityEvent.php', 'OCP\\WorkflowEngine\\IFileCheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IFileCheck.php', 'OCP\\WorkflowEngine\\IManager' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IManager.php', 'OCP\\WorkflowEngine\\IOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IOperation.php', + 'OCP\\WorkflowEngine\\IRuleMatcher' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IRuleMatcher.php', 'OCP\\WorkflowEngine\\ISpecificOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ISpecificOperation.php', 'OC\\Accounts\\Account' => __DIR__ . '/../../..' . '/lib/private/Accounts/Account.php', 'OC\\Accounts\\AccountManager' => __DIR__ . '/../../..' . '/lib/private/Accounts/AccountManager.php', diff --git a/lib/public/WorkflowEngine/ICheck.php b/lib/public/WorkflowEngine/ICheck.php index 6e9c2fc765e..f5586e83d51 100644 --- a/lib/public/WorkflowEngine/ICheck.php +++ b/lib/public/WorkflowEngine/ICheck.php @@ -70,24 +70,4 @@ interface ICheck { * @since 18.0.0 */ public function isAvailableForScope(int $scope): bool; - - /** - * Equips the check with a subject fitting the Entity. For instance, an - * entity of File will receive an instance of OCP\Files\Node, or a comment - * entity might get an IComment. - * - * The implementing check must be aware of the incoming type. - * - * If an unsupported subject is passed the implementation MAY throw an - * \UnexpectedValueException. - * - * When an implementation does not depend on a subject being passed to it, - * for example RequestTime, the implemented method SHOULD just pass, without - * any further operation. - * - * @param IEntity $entity - * @param mixed $subject - * @throws \UnexpectedValueException - */ - public function setEntitySubject(IEntity $entity, $subject): void; } diff --git a/lib/public/WorkflowEngine/IEntityCheck.php b/lib/public/WorkflowEngine/IEntityCheck.php new file mode 100644 index 00000000000..82687a052ba --- /dev/null +++ b/lib/public/WorkflowEngine/IEntityCheck.php @@ -0,0 +1,53 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\WorkflowEngine; + + +use OCP\Files\Storage\IStorage; + +/** + * Interface IFileCheck + * + * @package OCP\WorkflowEngine + * @since 18.0.0 + */ +interface IEntityCheck { + /** + * Equips the check with a subject fitting the Entity. For instance, an + * entity of File will receive an instance of OCP\Files\Node, or a comment + * entity might get an IComment. + * + * The implementing check must be aware of the incoming type. + * + * If an unsupported subject is passed the implementation MAY throw an + * \UnexpectedValueException. + * + * @param IEntity $entity + * @param mixed $subject + * @throws \UnexpectedValueException + */ + public function setEntitySubject(IEntity $entity, $subject): void; + +} diff --git a/lib/public/WorkflowEngine/IFileCheck.php b/lib/public/WorkflowEngine/IFileCheck.php index ce15d6c352c..557ba0f3c10 100644 --- a/lib/public/WorkflowEngine/IFileCheck.php +++ b/lib/public/WorkflowEngine/IFileCheck.php @@ -33,12 +33,10 @@ use OCP\Files\Storage\IStorage; * @package OCP\WorkflowEngine * @since 18.0.0 */ -interface IFileCheck { +interface IFileCheck extends IEntityCheck { /** - * @param IStorage $storage - * @param string $path * @since 18.0.0 */ - public function setFileInfo(IStorage $storage, $path); + public function setFileInfo(IStorage $storage, string $path); } diff --git a/lib/public/WorkflowEngine/IManager.php b/lib/public/WorkflowEngine/IManager.php index 8be47d961e7..78fd718ec9e 100644 --- a/lib/public/WorkflowEngine/IManager.php +++ b/lib/public/WorkflowEngine/IManager.php @@ -23,9 +23,6 @@ namespace OCP\WorkflowEngine; - -use OCP\Files\Storage\IStorage; - /** * Interface IManager * @@ -41,21 +38,6 @@ interface IManager { const EVENT_NAME_REG_ENTITY = 'OCP\WorkflowEngine::registerEntities'; const EVENT_NAME_REG_CHECK = 'OCP\WorkflowEngine::registerChecks'; - /** - * @param IStorage $storage - * @param string $path - * @since 9.1 - */ - public function setFileInfo(IStorage $storage, $path); - - /** - * @param string $class - * @param bool $returnFirstMatchingOperationOnly - * @return array - * @since 9.1 - */ - public function getMatchingOperations($class, $returnFirstMatchingOperationOnly = true); - /** * Listen to `\OCP\WorkflowEngine::EVENT_NAME_REG_ENTITY` at the * EventDispatcher for registering your entities. @@ -79,4 +61,9 @@ interface IManager { * @since 18.0.0 */ public function registerCheck(ICheck $check): void; + + /** + * @since 18.0.0 + */ + public function getRuleMatcher(): IRuleMatcher; } diff --git a/lib/public/WorkflowEngine/IEntityAware.php b/lib/public/WorkflowEngine/IRuleMatcher.php similarity index 85% rename from lib/public/WorkflowEngine/IEntityAware.php rename to lib/public/WorkflowEngine/IRuleMatcher.php index 5ef5066f9ad..5569800edb7 100644 --- a/lib/public/WorkflowEngine/IEntityAware.php +++ b/lib/public/WorkflowEngine/IRuleMatcher.php @@ -25,16 +25,15 @@ declare(strict_types=1); namespace OCP\WorkflowEngine; /** - * Interface IEntityAware + * Class IRuleMatcher * * @package OCP\WorkflowEngine * * @since 18.0.0 */ -interface IEntityAware { +interface IRuleMatcher extends IFileCheck { /** - * @param object $entity * @since 18.0.0 */ - public function setEntity($entity); + public function getMatchingOperations(string $class, bool $returnFirstMatchingOperationOnly = true): array; } From ae1cc1d14dda99f62714d5e658e6cb71378aaae2 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 9 Sep 2019 17:17:39 +0200 Subject: [PATCH 32/68] entities equip the RuleMatcher on the events they are aware of Operations will receive the matcher instance Signed-off-by: Arthur Schiwon --- .../lib/AppInfo/Application.php | 11 ++++++++--- apps/workflowengine/lib/Entity/File.php | 19 +++++++++++++++++++ lib/public/WorkflowEngine/IEntity.php | 7 +++++++ lib/public/WorkflowEngine/IOperation.php | 7 ++++--- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/apps/workflowengine/lib/AppInfo/Application.php b/apps/workflowengine/lib/AppInfo/Application.php index e691c53d528..f5653a6e48c 100644 --- a/apps/workflowengine/lib/AppInfo/Application.php +++ b/apps/workflowengine/lib/AppInfo/Application.php @@ -24,6 +24,7 @@ namespace OCA\WorkflowEngine\AppInfo; use OCA\WorkflowEngine\Manager; use OCP\Template; use OCA\WorkflowEngine\Controller\RequestTime; +use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IOperation; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -83,13 +84,17 @@ class Application extends \OCP\AppFramework\App { foreach ($configuredEvents as $operationClass => $events) { foreach ($events as $entityClass => $eventNames) { - array_map(function (string $eventName) use ($operationClass) { + array_map(function (string $eventName) use ($operationClass, $entityClass) { $this->dispatcher->addListener( $eventName, - function (GenericEvent $event) use ($eventName, $operationClass) { + function (GenericEvent $event) use ($eventName, $operationClass, $entityClass) { + $ruleMatcher = $this->manager->getRuleMatcher(); + /** @var IEntity $entity */ + $entity = $this->getContainer()->query($entityClass); + $entity->prepareRuleMatcher($ruleMatcher, $eventName, $event); /** @var IOperation $operation */ $operation = $this->getContainer()->query($operationClass); - $operation->onEvent($eventName, $event); + $operation->onEvent($eventName, $event, $ruleMatcher); } ); }, $eventNames); diff --git a/apps/workflowengine/lib/Entity/File.php b/apps/workflowengine/lib/Entity/File.php index b420217c4b6..dd15b0fd435 100644 --- a/apps/workflowengine/lib/Entity/File.php +++ b/apps/workflowengine/lib/Entity/File.php @@ -28,6 +28,8 @@ use OCP\IL10N; use OCP\IURLGenerator; use OCP\WorkflowEngine\GenericEntityEvent; use OCP\WorkflowEngine\IEntity; +use OCP\WorkflowEngine\IRuleMatcher; +use Symfony\Component\EventDispatcher\GenericEvent; class File implements IEntity { @@ -60,4 +62,21 @@ class File implements IEntity { new GenericEntityEvent($this->l10n->t('File copied'), $namespace . 'postCopy' ), ]; } + + /** + * @since 18.0.0 + */ + public function prepareRuleMatcher(IRuleMatcher $ruleMatcher, string $eventName, GenericEvent $event): void { + switch ($eventName) { + case 'postCreate': + case 'postWrite': + case 'postDelete': + case 'postTouch': + $ruleMatcher->setEntitySubject($this, $event->getSubject()); + break; + case 'postRename': + case 'postCopy': + $ruleMatcher->setEntitySubject($this, $event->getSubject()[1]); + } + } } diff --git a/lib/public/WorkflowEngine/IEntity.php b/lib/public/WorkflowEngine/IEntity.php index 5ac1082a5a3..c08e9072a38 100644 --- a/lib/public/WorkflowEngine/IEntity.php +++ b/lib/public/WorkflowEngine/IEntity.php @@ -24,6 +24,8 @@ declare(strict_types=1); namespace OCP\WorkflowEngine; +use Symfony\Component\EventDispatcher\GenericEvent; + /** * Interface IEntity * @@ -67,4 +69,9 @@ interface IEntity { */ public function getEvents(): array; + /** + * @since 18.0.0 + */ + public function prepareRuleMatcher(IRuleMatcher $ruleMatcher, string $eventName, GenericEvent $event): void; + } diff --git a/lib/public/WorkflowEngine/IOperation.php b/lib/public/WorkflowEngine/IOperation.php index 8bba92351a2..d16fd618a84 100644 --- a/lib/public/WorkflowEngine/IOperation.php +++ b/lib/public/WorkflowEngine/IOperation.php @@ -91,12 +91,13 @@ interface IOperation { * Is being called by the workflow engine when an event was triggered that * is configured for this operation. An evaluation whether the event * qualifies for this operation to run has still to be done by the - * implementor. + * implementor by calling the RuleMatchers getMatchingOperations method + * and evaluating the results. * - * If the implementor is an IComplexOpe ration, this method will not be + * If the implementor is an IComplexOperation, this method will not be * called automatically. It can be used or left as no-op by the implementor. * * @since 18.0.0 */ - public function onEvent(string $eventName, GenericEvent $event): void; + public function onEvent(string $eventName, GenericEvent $event, IRuleMatcher $ruleMatcher): void; } From 3a4e31ef6beed45cc6985d95583e33f198b10e40 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 9 Sep 2019 17:23:22 +0200 Subject: [PATCH 33/68] fix missing @since's Signed-off-by: Arthur Schiwon --- lib/public/WorkflowEngine/GenericEntityEvent.php | 12 ++++++++++++ lib/public/WorkflowEngine/IEntityCheck.php | 1 + 2 files changed, 13 insertions(+) diff --git a/lib/public/WorkflowEngine/GenericEntityEvent.php b/lib/public/WorkflowEngine/GenericEntityEvent.php index f556eb93b22..3ea34c6fb87 100644 --- a/lib/public/WorkflowEngine/GenericEntityEvent.php +++ b/lib/public/WorkflowEngine/GenericEntityEvent.php @@ -24,6 +24,13 @@ declare(strict_types=1); namespace OCP\WorkflowEngine; +/** + * Class GenericEntityEvent + * + * @package OCP\WorkflowEngine + * + * @since 18.0.0 + */ class GenericEntityEvent implements IEntityEvent { /** @var string */ @@ -31,6 +38,11 @@ class GenericEntityEvent implements IEntityEvent { /** @var string */ private $eventName; + /** + * GenericEntityEvent constructor. + * + * @since 18.0.0 + */ public function __construct(string $displayName, string $eventName) { if(trim($displayName) === '') { throw new \InvalidArgumentException('DisplayName must not be empty'); diff --git a/lib/public/WorkflowEngine/IEntityCheck.php b/lib/public/WorkflowEngine/IEntityCheck.php index 82687a052ba..7a4df0afd5f 100644 --- a/lib/public/WorkflowEngine/IEntityCheck.php +++ b/lib/public/WorkflowEngine/IEntityCheck.php @@ -47,6 +47,7 @@ interface IEntityCheck { * @param IEntity $entity * @param mixed $subject * @throws \UnexpectedValueException + * @since 18.0.0 */ public function setEntitySubject(IEntity $entity, $subject): void; From 1cc6f34d889f9a4f2a3721bf1e988c4ce4a56f11 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 9 Sep 2019 23:28:51 +0200 Subject: [PATCH 34/68] adapt file hooks test to eventdispatcher utilization Signed-off-by: Arthur Schiwon --- tests/lib/Files/Node/HookConnectorTest.php | 89 ++++++++++++++++------ 1 file changed, 66 insertions(+), 23 deletions(-) diff --git a/tests/lib/Files/Node/HookConnectorTest.php b/tests/lib/Files/Node/HookConnectorTest.php index 765bb647a1a..0d67fada3c0 100644 --- a/tests/lib/Files/Node/HookConnectorTest.php +++ b/tests/lib/Files/Node/HookConnectorTest.php @@ -9,12 +9,15 @@ namespace Test\Files\Node; use OC\Files\Filesystem; +use OC\Files\Node\HookConnector; use OC\Files\Node\Root; use OC\Files\Storage\Temporary; use OC\Files\View; use OCP\Files\Node; use OCP\ILogger; use OCP\IUserManager; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; use Test\TestCase; use Test\Traits\MountProviderTrait; use Test\Traits\UserTrait; @@ -29,6 +32,9 @@ use Test\Traits\UserTrait; class HookConnectorTest extends TestCase { use UserTrait; use MountProviderTrait; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + /** @var EventDispatcherInterface */ + protected $eventDispatcher; /** * @var View @@ -60,6 +66,7 @@ class HookConnectorTest extends TestCase { $this->createMock(ILogger::class), $this->createMock(IUserManager::class) ); + $this->eventDispatcher = \OC::$server->getEventDispatcher(); } public function tearDown() { @@ -72,50 +79,50 @@ class HookConnectorTest extends TestCase { return [ [function () { Filesystem::file_put_contents('test.txt', 'asd'); - }, 'preWrite'], + }, 'preWrite', '\OCP\Files::preWrite'], [function () { Filesystem::file_put_contents('test.txt', 'asd'); - }, 'postWrite'], + }, 'postWrite', '\OCP\Files::postWrite'], [function () { Filesystem::file_put_contents('test.txt', 'asd'); - }, 'preCreate'], + }, 'preCreate', '\OCP\Files::preCreate'], [function () { Filesystem::file_put_contents('test.txt', 'asd'); - }, 'postCreate'], + }, 'postCreate', '\OCP\Files::postCreate'], [function () { Filesystem::mkdir('test.txt'); - }, 'preCreate'], + }, 'preCreate', '\OCP\Files::preCreate'], [function () { Filesystem::mkdir('test.txt'); - }, 'postCreate'], + }, 'postCreate', '\OCP\Files::postCreate'], [function () { Filesystem::touch('test.txt'); - }, 'preTouch'], + }, 'preTouch', '\OCP\Files::preTouch'], [function () { Filesystem::touch('test.txt'); - }, 'postTouch'], + }, 'postTouch', '\OCP\Files::postTouch'], [function () { Filesystem::touch('test.txt'); - }, 'preCreate'], + }, 'preCreate', '\OCP\Files::preCreate'], [function () { Filesystem::touch('test.txt'); - }, 'postCreate'], + }, 'postCreate', '\OCP\Files::postCreate'], [function () { Filesystem::file_put_contents('test.txt', 'asd'); Filesystem::unlink('test.txt'); - }, 'preDelete'], + }, 'preDelete', '\OCP\Files::preDelete'], [function () { Filesystem::file_put_contents('test.txt', 'asd'); Filesystem::unlink('test.txt'); - }, 'postDelete'], + }, 'postDelete', '\OCP\Files::postDelete'], [function () { Filesystem::mkdir('test.txt'); Filesystem::rmdir('test.txt'); - }, 'preDelete'], + }, 'preDelete', '\OCP\Files::preDelete'], [function () { Filesystem::mkdir('test.txt'); Filesystem::rmdir('test.txt'); - }, 'postDelete'], + }, 'postDelete', '\OCP\Files::postDelete'], ]; } @@ -124,8 +131,8 @@ class HookConnectorTest extends TestCase { * @param string $expectedHook * @dataProvider viewToNodeProvider */ - public function testViewToNode(callable $operation, $expectedHook) { - $connector = new \OC\Files\Node\HookConnector($this->root, $this->view); + public function testViewToNode(callable $operation, $expectedHook, $expectedEvent) { + $connector = new HookConnector($this->root, $this->view, $this->eventDispatcher); $connector->viewToNode(); $hookCalled = false; /** @var Node $hookNode */ @@ -136,10 +143,21 @@ class HookConnectorTest extends TestCase { $hookNode = $node; }); + $dispatcherCalled = false; + /** @var Node $dispatcherNode */ + $dispatcherNode = null; + $this->eventDispatcher->addListener($expectedEvent, function (GenericEvent $event) use (&$dispatcherCalled, &$dispatcherNode) { + $dispatcherCalled = true; + $dispatcherNode = $event->getSubject(); + }); + $operation(); $this->assertTrue($hookCalled); $this->assertEquals('/' . $this->userId . '/files/test.txt', $hookNode->getPath()); + + $this->assertTrue($dispatcherCalled); + $this->assertEquals('/' . $this->userId . '/files/test.txt', $dispatcherNode->getPath()); } public function viewToNodeProviderCopyRename() { @@ -147,19 +165,19 @@ class HookConnectorTest extends TestCase { [function () { Filesystem::file_put_contents('source', 'asd'); Filesystem::rename('source', 'target'); - }, 'preRename'], + }, 'preRename', '\OCP\Files::preRename'], [function () { Filesystem::file_put_contents('source', 'asd'); Filesystem::rename('source', 'target'); - }, 'postRename'], + }, 'postRename', '\OCP\Files::postRename'], [function () { Filesystem::file_put_contents('source', 'asd'); Filesystem::copy('source', 'target'); - }, 'preCopy'], + }, 'preCopy', '\OCP\Files::preCopy'], [function () { Filesystem::file_put_contents('source', 'asd'); Filesystem::copy('source', 'target'); - }, 'postCopy'], + }, 'postCopy', '\OCP\Files::postCopy'], ]; } @@ -168,8 +186,8 @@ class HookConnectorTest extends TestCase { * @param string $expectedHook * @dataProvider viewToNodeProviderCopyRename */ - public function testViewToNodeCopyRename(callable $operation, $expectedHook) { - $connector = new \OC\Files\Node\HookConnector($this->root, $this->view); + public function testViewToNodeCopyRename(callable $operation, $expectedHook, $expectedEvent) { + $connector = new HookConnector($this->root, $this->view, $this->eventDispatcher); $connector->viewToNode(); $hookCalled = false; /** @var Node $hookSourceNode */ @@ -183,15 +201,29 @@ class HookConnectorTest extends TestCase { $hookTargetNode = $targetNode; }); + $dispatcherCalled = false; + /** @var Node $dispatcherSourceNode */ + $dispatcherSourceNode = null; + /** @var Node $dispatcherTargetNode */ + $dispatcherTargetNode = null; + $this->eventDispatcher->addListener($expectedEvent, function (GenericEvent $event) use (&$dispatcherSourceNode, &$dispatcherTargetNode, &$dispatcherCalled) { + $dispatcherCalled = true; + list($dispatcherSourceNode, $dispatcherTargetNode) = $event->getSubject(); + }); + $operation(); $this->assertTrue($hookCalled); $this->assertEquals('/' . $this->userId . '/files/source', $hookSourceNode->getPath()); $this->assertEquals('/' . $this->userId . '/files/target', $hookTargetNode->getPath()); + + $this->assertTrue($dispatcherCalled); + $this->assertEquals('/' . $this->userId . '/files/source', $dispatcherSourceNode->getPath()); + $this->assertEquals('/' . $this->userId . '/files/target', $dispatcherTargetNode->getPath()); } public function testPostDeleteMeta() { - $connector = new \OC\Files\Node\HookConnector($this->root, $this->view); + $connector = new HookConnector($this->root, $this->view, $this->eventDispatcher); $connector->viewToNode(); $hookCalled = false; /** @var Node $hookNode */ @@ -202,11 +234,22 @@ class HookConnectorTest extends TestCase { $hookNode = $node; }); + $dispatcherCalled = false; + /** @var Node $dispatcherNode */ + $dispatcherNode = null; + $this->eventDispatcher->addListener('\OCP\Files::postDelete', function (GenericEvent $event) use (&$dispatcherCalled, &$dispatcherNode) { + $dispatcherCalled = true; + $dispatcherNode = $event->getSubject(); + }); + Filesystem::file_put_contents('test.txt', 'asd'); $info = Filesystem::getFileInfo('test.txt'); Filesystem::unlink('test.txt'); $this->assertTrue($hookCalled); $this->assertEquals($hookNode->getId(), $info->getId()); + + $this->assertTrue($dispatcherCalled); + $this->assertEquals($dispatcherNode->getId(), $info->getId()); } } From 5610f73b7a57c733f437c9b374b5c1d705bd95ed Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 9 Sep 2019 23:59:31 +0200 Subject: [PATCH 35/68] File entity supports tagging events now Signed-off-by: Arthur Schiwon --- apps/workflowengine/lib/Entity/File.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/workflowengine/lib/Entity/File.php b/apps/workflowengine/lib/Entity/File.php index dd15b0fd435..8fb035afacc 100644 --- a/apps/workflowengine/lib/Entity/File.php +++ b/apps/workflowengine/lib/Entity/File.php @@ -24,8 +24,10 @@ declare(strict_types=1); namespace OCA\WorkflowEngine\Entity; +use OCP\Files\IRootFolder; use OCP\IL10N; use OCP\IURLGenerator; +use OCP\SystemTag\MapperEvent; use OCP\WorkflowEngine\GenericEntityEvent; use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IRuleMatcher; @@ -37,10 +39,13 @@ class File implements IEntity { protected $l10n; /** @var IURLGenerator */ protected $urlGenerator; + /** @var IRootFolder */ + private $root; - public function __construct(IL10N $l10n, IURLGenerator $urlGenerator) { + public function __construct(IL10N $l10n, IURLGenerator $urlGenerator, IRootFolder $root) { $this->l10n = $l10n; $this->urlGenerator = $urlGenerator; + $this->root = $root; } public function getName(): string { @@ -60,6 +65,7 @@ class File implements IEntity { new GenericEntityEvent($this->l10n->t('File deleted'), $namespace . 'postDelete' ), new GenericEntityEvent($this->l10n->t('File accessed'), $namespace . 'postTouch' ), new GenericEntityEvent($this->l10n->t('File copied'), $namespace . 'postCopy' ), + new GenericEntityEvent($this->l10n->t('Tag assigned'), MapperEvent::EVENT_ASSIGN ), ]; } @@ -77,6 +83,17 @@ class File implements IEntity { case 'postRename': case 'postCopy': $ruleMatcher->setEntitySubject($this, $event->getSubject()[1]); + break; + case MapperEvent::EVENT_ASSIGN: + if(!$event instanceof MapperEvent || $event->getObjectType() !== 'files') { + break; + } + $nodes = $this->root->getById((int)$event->getObjectId()); + if(is_array($nodes) && !empty($nodes)) { + $node = array_shift($nodes); + $ruleMatcher->setEntitySubject($this, $node); + } + break; } } } From d36365413210fc3adfd1214b174e68d8a340ee96 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 10 Sep 2019 00:52:27 +0200 Subject: [PATCH 36/68] fix order/entity types when setting the context Signed-off-by: Arthur Schiwon --- apps/workflowengine/lib/Service/RuleMatcher.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/workflowengine/lib/Service/RuleMatcher.php b/apps/workflowengine/lib/Service/RuleMatcher.php index bc92eeee818..bcfcd5dd219 100644 --- a/apps/workflowengine/lib/Service/RuleMatcher.php +++ b/apps/workflowengine/lib/Service/RuleMatcher.php @@ -36,6 +36,7 @@ use OCP\IServerContainer; use OCP\IUserSession; use OCP\WorkflowEngine\ICheck; use OCP\WorkflowEngine\IEntity; +use OCP\WorkflowEngine\IEntityCheck; use OCP\WorkflowEngine\IFileCheck; use OCP\WorkflowEngine\IManager; use OCP\WorkflowEngine\IRuleMatcher; @@ -117,21 +118,20 @@ class RuleMatcher implements IRuleMatcher { return true; } - if ($checkInstance instanceof ICheck) { + if ($checkInstance instanceof IFileCheck) { + if (empty($this->fileInfo)) { + throw new \RuntimeException('Must set file info before running the check'); + } + $checkInstance->setFileInfo($this->fileInfo['storage'], $this->fileInfo['path']); + } elseif ($checkInstance instanceof IEntityCheck) { foreach($this->contexts as $entityInfo) { list($entity, $subject) = $entityInfo; $checkInstance->setEntitySubject($entity, $subject); } - return $checkInstance->executeCheck($check['operator'], $check['value']); - } elseif ($checkInstance instanceof IFileCheck) { - if(empty($this->fileInfo)) { - throw new \RuntimeException('Must set file info before running the check'); - } - $checkInstance->setFileInfo($this->fileInfo['storage'], $this->fileInfo['path']); - return $checkInstance->executeCheck($check['operator'], $check['value']); } else { // Check is invalid throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class'])); } + return $checkInstance->executeCheck($check['operator'], $check['value']); } } From 72a7fe81854f5a76208e76ebcc127912283f94cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 10 Sep 2019 08:52:13 +0200 Subject: [PATCH 37/68] Fix type hinting on setFileInfo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/workflowengine/lib/Check/FileMimeType.php | 2 +- apps/workflowengine/lib/Check/TFileCheck.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/workflowengine/lib/Check/FileMimeType.php b/apps/workflowengine/lib/Check/FileMimeType.php index 92375d4d3cd..5bebaf9fc01 100644 --- a/apps/workflowengine/lib/Check/FileMimeType.php +++ b/apps/workflowengine/lib/Check/FileMimeType.php @@ -58,7 +58,7 @@ class FileMimeType extends AbstractStringCheck implements IFileCheck { * @param IStorage $storage * @param string $path */ - public function setFileInfo(IStorage $storage, $path) { + public function setFileInfo(IStorage $storage, string $path) { $this->_setFileInfo($storage, $path); if (!isset($this->mimeType[$this->storage->getId()][$this->path]) || $this->mimeType[$this->storage->getId()][$this->path] === '') { diff --git a/apps/workflowengine/lib/Check/TFileCheck.php b/apps/workflowengine/lib/Check/TFileCheck.php index 44a39aadfde..383c2d4ef5f 100644 --- a/apps/workflowengine/lib/Check/TFileCheck.php +++ b/apps/workflowengine/lib/Check/TFileCheck.php @@ -42,7 +42,7 @@ trait TFileCheck { * @param string $path * @since 18.0.0 */ - public function setFileInfo(IStorage $storage, $path) { + public function setFileInfo(IStorage $storage, string $path) { $this->storage = $storage; $this->path = $path; } From 9eb7a318649b3ca2bf85ccdb1db5ecb3be0bde70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 10 Sep 2019 08:59:51 +0200 Subject: [PATCH 38/68] Fix test mocking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/workflowengine/tests/ManagerTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/workflowengine/tests/ManagerTest.php b/apps/workflowengine/tests/ManagerTest.php index 3a0cd18a946..14700881a9c 100644 --- a/apps/workflowengine/tests/ManagerTest.php +++ b/apps/workflowengine/tests/ManagerTest.php @@ -26,6 +26,7 @@ use OC\L10N\L10N; use OCA\WorkflowEngine\Entity\File; use OCA\WorkflowEngine\Helper\ScopeContext; use OCA\WorkflowEngine\Manager; +use OCP\Files\IRootFolder; use OCP\IDBConnection; use OCP\IL10N; use OCP\ILogger; @@ -276,7 +277,7 @@ class ManagerTest extends TestCase { return $this->createMock(IOperation::class); } else if($class === File::class) { return $this->getMockBuilder(File::class) - ->setConstructorArgs([$this->createMock(L10N::class), $this->createMock(IURLGenerator::class)]) + ->setConstructorArgs([$this->createMock(L10N::class), $this->createMock(IURLGenerator::class), $this->createMock(IRootFolder::class)]) ->setMethodsExcept(['getEvents']) ->getMock(); } From ad976c66fd9a78e6d90224091634b04a25a08f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 6 Aug 2019 17:40:30 +0200 Subject: [PATCH 39/68] Unified workflow management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/workflowengine/src/admin.js | 2 +- apps/workflowengine/src/components/Check.vue | 104 +++++++++ apps/workflowengine/src/components/Event.vue | 84 +++++++ .../src/components/Operation.vue | 83 +++++++ .../components/Operations/ConvertToPdf.vue | 45 ++++ .../src/components/Operations/Tag.vue | 29 +++ apps/workflowengine/src/components/Rule.vue | 209 ++++++++++++++++++ .../src/components/Values/FileMimeType.vue | 39 ++++ .../src/components/Values/SizeValue.vue | 28 +++ .../src/components/Workflow.vue | 146 ++++++++++++ apps/workflowengine/src/filemimetypeplugin.js | 5 + apps/workflowengine/src/filesizeplugin.js | 5 +- apps/workflowengine/src/services/Operation.js | 141 ++++++++++++ apps/workflowengine/src/workflowengine.js | 9 +- 14 files changed, 926 insertions(+), 3 deletions(-) create mode 100644 apps/workflowengine/src/components/Check.vue create mode 100644 apps/workflowengine/src/components/Event.vue create mode 100644 apps/workflowengine/src/components/Operation.vue create mode 100644 apps/workflowengine/src/components/Operations/ConvertToPdf.vue create mode 100644 apps/workflowengine/src/components/Operations/Tag.vue create mode 100644 apps/workflowengine/src/components/Rule.vue create mode 100644 apps/workflowengine/src/components/Values/FileMimeType.vue create mode 100644 apps/workflowengine/src/components/Values/SizeValue.vue create mode 100644 apps/workflowengine/src/components/Workflow.vue create mode 100644 apps/workflowengine/src/services/Operation.js diff --git a/apps/workflowengine/src/admin.js b/apps/workflowengine/src/admin.js index 92f485a8b4c..fb2af941436 100644 --- a/apps/workflowengine/src/admin.js +++ b/apps/workflowengine/src/admin.js @@ -382,4 +382,4 @@ import OperationsTemplate from './templates/operations.handlebars'; this.collection.each(this.renderOperation, this); } }); -})(); +})(); \ No newline at end of file diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue new file mode 100644 index 00000000000..c8c7c46aa87 --- /dev/null +++ b/apps/workflowengine/src/components/Check.vue @@ -0,0 +1,104 @@ + + + + + \ No newline at end of file diff --git a/apps/workflowengine/src/components/Event.vue b/apps/workflowengine/src/components/Event.vue new file mode 100644 index 00000000000..fd5097cecfc --- /dev/null +++ b/apps/workflowengine/src/components/Event.vue @@ -0,0 +1,84 @@ + + + + + \ No newline at end of file diff --git a/apps/workflowengine/src/components/Operation.vue b/apps/workflowengine/src/components/Operation.vue new file mode 100644 index 00000000000..f7a8f56cede --- /dev/null +++ b/apps/workflowengine/src/components/Operation.vue @@ -0,0 +1,83 @@ + + + + + \ No newline at end of file diff --git a/apps/workflowengine/src/components/Operations/ConvertToPdf.vue b/apps/workflowengine/src/components/Operations/ConvertToPdf.vue new file mode 100644 index 00000000000..62c53a4ee6c --- /dev/null +++ b/apps/workflowengine/src/components/Operations/ConvertToPdf.vue @@ -0,0 +1,45 @@ + + + + + \ No newline at end of file diff --git a/apps/workflowengine/src/components/Operations/Tag.vue b/apps/workflowengine/src/components/Operations/Tag.vue new file mode 100644 index 00000000000..74e4e4f977b --- /dev/null +++ b/apps/workflowengine/src/components/Operations/Tag.vue @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue new file mode 100644 index 00000000000..818e15610fa --- /dev/null +++ b/apps/workflowengine/src/components/Rule.vue @@ -0,0 +1,209 @@ + + + + + \ No newline at end of file diff --git a/apps/workflowengine/src/components/Values/FileMimeType.vue b/apps/workflowengine/src/components/Values/FileMimeType.vue new file mode 100644 index 00000000000..70b8f0d984b --- /dev/null +++ b/apps/workflowengine/src/components/Values/FileMimeType.vue @@ -0,0 +1,39 @@ + + + + + \ No newline at end of file diff --git a/apps/workflowengine/src/components/Values/SizeValue.vue b/apps/workflowengine/src/components/Values/SizeValue.vue new file mode 100644 index 00000000000..bc4378702e1 --- /dev/null +++ b/apps/workflowengine/src/components/Values/SizeValue.vue @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/apps/workflowengine/src/components/Workflow.vue b/apps/workflowengine/src/components/Workflow.vue new file mode 100644 index 00000000000..9993b328827 --- /dev/null +++ b/apps/workflowengine/src/components/Workflow.vue @@ -0,0 +1,146 @@ + + + + + \ No newline at end of file diff --git a/apps/workflowengine/src/filemimetypeplugin.js b/apps/workflowengine/src/filemimetypeplugin.js index 17c092d209f..6b929b2aad5 100644 --- a/apps/workflowengine/src/filemimetypeplugin.js +++ b/apps/workflowengine/src/filemimetypeplugin.js @@ -17,6 +17,7 @@ * along with this program. If not, see . * */ +import FileMimeType from './components/Values/FileMimeType' (function() { @@ -65,6 +66,10 @@ var regexRegex = /^\/(.*)\/([gui]{0,3})$/, result = regexRegex.exec(string); return result !== null; + }, + + component: function () { + return FileMimeType } }; })(); diff --git a/apps/workflowengine/src/filesizeplugin.js b/apps/workflowengine/src/filesizeplugin.js index 0efa9d00edf..ebd72dcd2f4 100644 --- a/apps/workflowengine/src/filesizeplugin.js +++ b/apps/workflowengine/src/filesizeplugin.js @@ -17,7 +17,7 @@ * along with this program. If not, see . * */ - +import SizeValue from './components/Values/SizeValue' (function() { OCA.WorkflowEngine = OCA.WorkflowEngine || {}; @@ -49,6 +49,9 @@ .tooltip({ placement: 'bottom' }); + }, + component: function () { + return SizeValue } }; })(); diff --git a/apps/workflowengine/src/services/Operation.js b/apps/workflowengine/src/services/Operation.js new file mode 100644 index 00000000000..1de7d2a95ad --- /dev/null +++ b/apps/workflowengine/src/services/Operation.js @@ -0,0 +1,141 @@ +import ConvertToPdf from './../components/Operations/ConvertToPdf' +import Tag from './../components/Operations/Tag' +class OperationService { + + constructor() { + this.operations = {} + } + registerOperation (operation) { + this.operations[operation.class] = Object.assign({ + color: 'var(--color-primary)' + }, operation) + } + + getAll() { + return this.operations + } + + get(className) { + return this.operations[className] + } + +} + +class EventService { + + constructor() { + this.events = {} + } + registerEvent(event) { + this.events[event.id] = event + } + + getAll() { + return this.events + } + + get(id) { + return this.events[id] + } + +} + +const operationService = new OperationService() +const eventService = new EventService() + + +const ALL_CHECKS = [ + 'OCA\\WorkflowEngine\\Check\\FileMimeType', + 'OCA\\WorkflowEngine\\Check\\FileName', + 'OCA\\WorkflowEngine\\Check\\FileSize', + 'OCA\\WorkflowEngine\\Check\\FileSystemTags', + 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress', + 'OCA\\WorkflowEngine\\Check\\RequestTime', + 'OCA\\WorkflowEngine\\Check\\RequestURL', + 'OCA\\WorkflowEngine\\Check\\RequestUserAgent', + 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' +] + +/** + * TODO: move to separate apps + * TODO: fetch from initial state api + **/ +const EVENT_FILE_ACCESS = 'EVENT_FILE_ACCESS' +const EVENT_FILE_CHANGED = 'EVENT_FILE_CHANGED' +const EVENT_FILE_TAGGED = 'EVENT_FILE_TAGGED' + +eventService.registerEvent({ + id: EVENT_FILE_ACCESS, + name: 'File is accessed', + icon: 'icon-desktop', + checks: ALL_CHECKS, +}) + +eventService.registerEvent({ + id: EVENT_FILE_CHANGED, + name: 'File was updated', + icon: 'icon-folder', + checks: ALL_CHECKS, +}) + + +eventService.registerEvent({ + id: EVENT_FILE_TAGGED, + name: 'File was tagged', + icon: 'icon-tag', + checks: ALL_CHECKS, +}) + +operationService.registerOperation({ + class: 'OCA\\FilesAccessControl\\Operation', + title: 'Block access', + description: 'todo', + icon: 'icon-block', + color: 'var(--color-error)', + events: [ + EVENT_FILE_ACCESS + ], + operation: 'deny' +}) + +operationService.registerOperation({ + class: 'OCA\\FilesAutomatedTagging\\Operation', + title: 'Tag a file', + description: 'todo', + icon: 'icon-tag', + events: [ + EVENT_FILE_CHANGED, + EVENT_FILE_TAGGED + ], + options: Tag + +}) + +operationService.registerOperation({ + class: 'OCA\\WorkflowPDFConverter\\Operation', + title: 'Convert to PDF', + description: 'todo', + color: '#dc5047', + icon: 'icon-convert-pdf', + events: [ + EVENT_FILE_CHANGED, + //EVENT_FILE_TAGGED + ], + options: ConvertToPdf +}) + + +const legacyChecks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => { + if (plugin.component) { + return {...plugin.getCheck(), component: plugin.component} + } + return plugin.getCheck() +}).reduce((obj, item) => { + obj[item.class] = item + return obj +}, {}) + +export { + eventService, + operationService +} \ No newline at end of file diff --git a/apps/workflowengine/src/workflowengine.js b/apps/workflowengine/src/workflowengine.js index 207d2311bcc..6c7af2a446e 100644 --- a/apps/workflowengine/src/workflowengine.js +++ b/apps/workflowengine/src/workflowengine.js @@ -1,4 +1,3 @@ -import './admin' import './filemimetypeplugin' import './filenameplugin' import './filesizeplugin' @@ -10,3 +9,11 @@ import './requestuseragentplugin' import './usergroupmembershipplugin' window.OCA.WorkflowEngine = OCA.WorkflowEngine + +import Vue from 'vue'; + +Vue.prototype.t = t; + +import Settings from './components/Workflow'; +const View = Vue.extend(Settings) +new View({}).$mount('#workflowengine') \ No newline at end of file From e7e9166efe2771b91a0ffdbe96f82284787244b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 9 Aug 2019 12:28:59 +0200 Subject: [PATCH 40/68] Add endpoint to test operations before submitting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/workflowengine/lib/Manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index efe6c387059..07438b2f7cb 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -425,7 +425,7 @@ class Manager implements IManager { * @param string $operation * @throws \UnexpectedValueException */ - protected function validateOperation($class, $name, array $checks, $operation, string $entity, array $events) { + public function validateOperation($class, $name, array $checks, $operation, string $entity, array $events) { try { /** @var IOperation $instance */ $instance = $this->container->query($class); From aed630ac0a63771c7a217a3c1d400a354929fafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Mon, 19 Aug 2019 17:09:58 +0200 Subject: [PATCH 41/68] Adjust template id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/workflowengine/templates/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/workflowengine/templates/settings.php b/apps/workflowengine/templates/settings.php index 04f43bb2573..576642b543a 100644 --- a/apps/workflowengine/templates/settings.php +++ b/apps/workflowengine/templates/settings.php @@ -22,4 +22,4 @@ /** @var array $_ */ /** @var \OCP\IL10N $l */ ?> -
+
From 9f8ccf1036ff18d961949bfb644c9d445caa8a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Mon, 19 Aug 2019 17:13:33 +0200 Subject: [PATCH 42/68] Use entity/event definitions from backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/workflowengine/src/components/Check.vue | 16 +- apps/workflowengine/src/components/Event.vue | 24 ++- .../src/components/Operation.vue | 25 +-- apps/workflowengine/src/components/Rule.vue | 8 +- .../src/components/Workflow.vue | 21 ++- apps/workflowengine/src/services/Operation.js | 168 +++++++++--------- 6 files changed, 141 insertions(+), 121 deletions(-) diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue index c8c7c46aa87..8cfe5a89020 100644 --- a/apps/workflowengine/src/components/Check.vue +++ b/apps/workflowengine/src/components/Check.vue @@ -15,6 +15,7 @@ \ No newline at end of file + diff --git a/apps/workflowengine/src/services/Operation.js b/apps/workflowengine/src/services/Operation.js index 1de7d2a95ad..a24d97ef226 100644 --- a/apps/workflowengine/src/services/Operation.js +++ b/apps/workflowengine/src/services/Operation.js @@ -1,5 +1,41 @@ import ConvertToPdf from './../components/Operations/ConvertToPdf' import Tag from './../components/Operations/Tag' + +const ALL_CHECKS = [ + 'OCA\\WorkflowEngine\\Check\\FileMimeType', + 'OCA\\WorkflowEngine\\Check\\FileName', + 'OCA\\WorkflowEngine\\Check\\FileSize', + 'OCA\\WorkflowEngine\\Check\\FileSystemTags', + 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress', + 'OCA\\WorkflowEngine\\Check\\RequestTime', + 'OCA\\WorkflowEngine\\Check\\RequestURL', + 'OCA\\WorkflowEngine\\Check\\RequestUserAgent', + 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' +] + +const Entities = OCP.InitialState.loadState('workflowengine', 'entities').map(entity => { + return { + ...entity, + // TODO: see if we should have this defined in the backend as well + checks: [...ALL_CHECKS] + } +}) + +const Checks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => { + if (plugin.component) { + return {...plugin.getCheck(), component: plugin.component} + } + return plugin.getCheck() +}).reduce((obj, item) => { + obj[item.class] = item + return obj +}, {}) + +/** + * Register operations + * TODO: should be provided by the backend + */ + class OperationService { constructor() { @@ -20,92 +56,63 @@ class OperationService { } } - -class EventService { - - constructor() { - this.events = {} - } - registerEvent(event) { - this.events[event.id] = event - } - - getAll() { - return this.events - } - - get(id) { - return this.events[id] - } - -} - const operationService = new OperationService() -const eventService = new EventService() - - -const ALL_CHECKS = [ - 'OCA\\WorkflowEngine\\Check\\FileMimeType', - 'OCA\\WorkflowEngine\\Check\\FileName', - 'OCA\\WorkflowEngine\\Check\\FileSize', - 'OCA\\WorkflowEngine\\Check\\FileSystemTags', - 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress', - 'OCA\\WorkflowEngine\\Check\\RequestTime', - 'OCA\\WorkflowEngine\\Check\\RequestURL', - 'OCA\\WorkflowEngine\\Check\\RequestUserAgent', - 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' -] - -/** - * TODO: move to separate apps - * TODO: fetch from initial state api - **/ -const EVENT_FILE_ACCESS = 'EVENT_FILE_ACCESS' -const EVENT_FILE_CHANGED = 'EVENT_FILE_CHANGED' -const EVENT_FILE_TAGGED = 'EVENT_FILE_TAGGED' - -eventService.registerEvent({ - id: EVENT_FILE_ACCESS, - name: 'File is accessed', - icon: 'icon-desktop', - checks: ALL_CHECKS, -}) - -eventService.registerEvent({ - id: EVENT_FILE_CHANGED, - name: 'File was updated', - icon: 'icon-folder', - checks: ALL_CHECKS, -}) - - -eventService.registerEvent({ - id: EVENT_FILE_TAGGED, - name: 'File was tagged', - icon: 'icon-tag', - checks: ALL_CHECKS, -}) operationService.registerOperation({ class: 'OCA\\FilesAccessControl\\Operation', title: 'Block access', - description: 'todo', + description: 'Deny access to files when they are accessed', icon: 'icon-block', color: 'var(--color-error)', + entites: [ + 'WorkflowEngine_Entity_File' + ], events: [ - EVENT_FILE_ACCESS + // TODO: this is probably handled differently since there is no regular event for files access control + 'WorkflowEngine_Entity_File::postTouch' ], operation: 'deny' }) +operationService.registerOperation({ + class: 'OCA\\TestExample\\Operation1', + title: 'Rename file', + description: '🚧 For UI mocking only', + icon: 'icon-address-white', + color: 'var(--color-success)', + entites: [], + events: [], + operation: 'deny' +}) +operationService.registerOperation({ + class: 'OCA\\TestExample\\Operation2', + title: 'Notify me', + description: '🚧 For UI mocking only', + icon: 'icon-comment-white', + color: 'var(--color-warning)', + entites: [], + events: [], + operation: 'deny' +}) +operationService.registerOperation({ + class: 'OCA\\TestExample\\Operation3', + title: 'Call a web hook', + description: '🚧 For UI mocking only', + icon: 'icon-category-integration icon-invert', + color: 'var(--color-primary)', + entites: [], + events: [], + operation: 'deny' +}) + operationService.registerOperation({ class: 'OCA\\FilesAutomatedTagging\\Operation', title: 'Tag a file', - description: 'todo', - icon: 'icon-tag', + description: 'Assign a tag to a file', + icon: 'icon-tag-white', events: [ - EVENT_FILE_CHANGED, - EVENT_FILE_TAGGED + 'WorkflowEngine_Entity_File::postWrite', + //'WorkflowEngine_Entity_File::postTagged', ], options: Tag @@ -114,28 +121,23 @@ operationService.registerOperation({ operationService.registerOperation({ class: 'OCA\\WorkflowPDFConverter\\Operation', title: 'Convert to PDF', - description: 'todo', + description: 'Generate a PDF file', color: '#dc5047', icon: 'icon-convert-pdf', events: [ - EVENT_FILE_CHANGED, + 'WorkflowEngine_Entity_File::postWrite', //EVENT_FILE_TAGGED ], options: ConvertToPdf }) +console.debug('[InitialState] Entities', Entities) +console.debug('[WorkflowEngine] Checks', Checks) +console.debug('[WorkflowEngine] Operations', operationService.operations) -const legacyChecks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => { - if (plugin.component) { - return {...plugin.getCheck(), component: plugin.component} - } - return plugin.getCheck() -}).reduce((obj, item) => { - obj[item.class] = item - return obj -}, {}) export { - eventService, + Entities, + Checks, operationService -} \ No newline at end of file +} From 0f84696d10c62e1e522457b876d4583c08d5e197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Mon, 19 Aug 2019 17:15:34 +0200 Subject: [PATCH 43/68] Styling fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/workflowengine/src/components/Rule.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue index 43cbbced209..59d66e43091 100644 --- a/apps/workflowengine/src/components/Rule.vue +++ b/apps/workflowengine/src/components/Rule.vue @@ -191,7 +191,7 @@ & > span { min-width: 50px; text-align: right; - color: var(--color-text-light); + color: var(--color-text-maxcontrast); padding-right: 5px; } .multiselect { From aa00f401b39c2b63cba7e5e8f6cdec8528466069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Wed, 28 Aug 2019 18:27:37 +0200 Subject: [PATCH 44/68] Adjust to new backend URLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/workflowengine/src/components/Check.vue | 2 +- apps/workflowengine/src/components/Event.vue | 16 +++++--- .../src/components/Operation.vue | 6 +++ apps/workflowengine/src/components/Rule.vue | 38 +++++++++++-------- .../src/components/Workflow.vue | 33 +++++++++------- apps/workflowengine/src/services/Operation.js | 4 +- 6 files changed, 62 insertions(+), 37 deletions(-) diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue index 8cfe5a89020..86005dae268 100644 --- a/apps/workflowengine/src/components/Check.vue +++ b/apps/workflowengine/src/components/Check.vue @@ -48,7 +48,7 @@ this.currentOption = Checks[this.check.class] this.currentOperator = this.operators.find((operator) => operator.operator === this.check.operator) this.$nextTick(() => { - this.$refs.checkSelector.$el.focus() + //this.$refs.checkSelector.$el.focus() }) }, computed: { diff --git a/apps/workflowengine/src/components/Event.vue b/apps/workflowengine/src/components/Event.vue index caf5c7631bd..7b39ea571fb 100644 --- a/apps/workflowengine/src/components/Event.vue +++ b/apps/workflowengine/src/components/Event.vue @@ -28,12 +28,15 @@ required: true } }, + mounted() { + this.updateEvent(this.currentEvent) + }, computed: { currentEvent() { - if (typeof this.rule.event === 'undefined') { + if (!this.rule.event) { return this.allEvents.length > 0 ? this.allEvents[0] : null } - return this.allEvents.find(event => event.id === this.rule.event) + return this.allEvents.find(event => event.entity === this.rule.entity && event.event === this.rule.event) }, allEvents() { return this.operation.events.map((entityEventName) => { @@ -45,7 +48,7 @@ return { entity: entityId, id: entityEventName, - event: eventName, + events: eventName, name: Event.displayName, icon: Entity.icon, checks: Entity.checks, @@ -58,8 +61,11 @@ }, methods: { updateEvent(event) { - this.$set(this.rule, 'event', event.id) - this.$emit('update', this.rule) + if (this.rule.entity !== event.entity || this.rule.events !== '["' + event.event + '"]') { + this.$set(this.rule, 'entity', event.entity) + this.$set(this.rule, 'event', event.event) + this.$emit('update', this.rule) + } } } } diff --git a/apps/workflowengine/src/components/Operation.vue b/apps/workflowengine/src/components/Operation.vue index 3e5de3198ca..30f26eb6ec4 100644 --- a/apps/workflowengine/src/components/Operation.vue +++ b/apps/workflowengine/src/components/Operation.vue @@ -71,6 +71,12 @@ } } + .actions__item:not(.colored) { + .icon { + filter: invert(1); + } + } + /* TODO: those should be provided by the backend, remove once ready */ .icon-block { background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' height='32' width='32' version='1.1' viewBox='0 0 32 32'%3E%3Cpath fill='%23fff' d='m10.203 2-8.203 8.203v11.594l8.203 8.203h11.594l8.203-8.203v-11.594l-8.203-8.203h-11.594zm11.097 5.3092 3.345 3.3448-5.346 5.346 5.346 5.346-3.299 3.299-5.346-5.346-5.346 5.346-3.2992-3.299 5.3462-5.346-5.3462-5.346 3.2992-3.2992 5.346 5.3462 5.3-5.3918z'/%3E%3C/svg%3E"); diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue index 59d66e43091..d1f5a088f51 100644 --- a/apps/workflowengine/src/components/Rule.vue +++ b/apps/workflowengine/src/components/Rule.vue @@ -1,5 +1,5 @@ diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue index 8a446dd7ae9..6a8757c5b3f 100644 --- a/apps/workflowengine/src/components/Rule.vue +++ b/apps/workflowengine/src/components/Rule.vue @@ -12,7 +12,7 @@

-

@@ -174,9 +174,10 @@ export default { .trigger, .action { flex-grow: 1; min-height: 100px; - max-width: 700px; + max-width: 900px; } .action { + max-width: 400px; position: relative; .buttons { position: absolute; @@ -212,7 +213,8 @@ export default { background-position: 7px center; background-color: transparent; padding-left: 6px; - width: 160px; + margin: 0; + width: 200px; border-radius: var(--border-radius); font-weight: normal; text-align: left; diff --git a/apps/workflowengine/src/components/Values/FileMimeType.vue b/apps/workflowengine/src/components/Values/FileMimeType.vue index 9913dc1e858..75729af8073 100644 --- a/apps/workflowengine/src/components/Values/FileMimeType.vue +++ b/apps/workflowengine/src/components/Values/FileMimeType.vue @@ -1,5 +1,5 @@ diff --git a/apps/workflowengine/src/legacy/filemimetypeplugin.js b/apps/workflowengine/src/legacy/filemimetypeplugin.js index 2b29c4fcbb8..e58bdec26d3 100644 --- a/apps/workflowengine/src/legacy/filemimetypeplugin.js +++ b/apps/workflowengine/src/legacy/filemimetypeplugin.js @@ -17,8 +17,6 @@ * along with this program. If not, see . * */ -import FileMimeType from './../components/Values/FileMimeType' - (function() { OCA.WorkflowEngine = OCA.WorkflowEngine || {} @@ -66,12 +64,6 @@ import FileMimeType from './../components/Values/FileMimeType' var regexRegex = /^\/(.*)\/([gui]{0,3})$/ var result = regexRegex.exec(string) return result !== null - }, - - component: function() { - return FileMimeType } } })() - -OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileMimeTypePlugin) diff --git a/apps/workflowengine/src/services/Operation.js b/apps/workflowengine/src/services/Operation.js index 99dca212f4c..ed996593cb4 100644 --- a/apps/workflowengine/src/services/Operation.js +++ b/apps/workflowengine/src/services/Operation.js @@ -13,16 +13,6 @@ const ALL_CHECKS = [ 'OCA\\WorkflowEngine\\Check\\UserGroupMembership' ] -const Checks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => { - if (plugin.component) { - return { ...plugin.getCheck(), component: plugin.component } - } - return plugin.getCheck() -}).reduce((obj, item) => { - obj[item.class] = item - return obj -}, {}) - const Operators = OCP.InitialState.loadState('workflowengine', 'operators') /** @@ -71,7 +61,6 @@ Operators['OCA\\FilesAutomatedTagging\\Operation'] = { } export { - Checks, Operators, ALL_CHECKS } diff --git a/apps/workflowengine/src/store.js b/apps/workflowengine/src/store.js index ec9d736dd08..34cee256757 100644 --- a/apps/workflowengine/src/store.js +++ b/apps/workflowengine/src/store.js @@ -35,6 +35,12 @@ const store = new Vuex.Store({ scope: OCP.InitialState.loadState('workflowengine', 'scope'), // TODO: move to backend data operations: Operators, + + plugins: Vue.observable({ + checks: {}, + operators: {} + }), + entities: OCP.InitialState.loadState('workflowengine', 'entities').map(entity => { return { ...entity, @@ -63,6 +69,12 @@ const store = new Vuex.Store({ removeRule(state, rule) { const index = state.rules.findIndex((item) => rule.id === item.id) state.rules.splice(index, 1) + }, + addPluginCheck(state, plugin) { + Vue.set(state.plugins.checks, plugin.class, plugin) + }, + addPluginOperator(state, plugin) { + Vue.set(state.plugins.operators, plugin.class, plugin) } }, actions: { diff --git a/apps/workflowengine/src/workflowengine.js b/apps/workflowengine/src/workflowengine.js index 866f049b0bf..bdf30397883 100644 --- a/apps/workflowengine/src/workflowengine.js +++ b/apps/workflowengine/src/workflowengine.js @@ -14,11 +14,41 @@ import Vuex from 'vuex' import store from './store' import Settings from './components/Workflow' +import FileMimeType from './components/Values/FileMimeType'; -window.OCA.WorkflowEngine = OCA.WorkflowEngine -Vue.use(Vuex) +window.OCA.WorkflowEngine = Object.assign({}, OCA.WorkflowEngine, { + registerCheck: function (Plugin) { + store.commit('addPluginCheck', Plugin) + }, + registerOperator: function (Plugin) { + store.commit('addPluginOperator', Plugin) + } +}) + +// Load legacy plugins for now and register them in the new plugin system +Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => { + if (plugin.component) { + return { ...plugin.getCheck(), component: plugin.component() } + } + return plugin.getCheck() +}).forEach((legacyCheckPlugin) => window.OCA.WorkflowEngine.registerCheck(legacyCheckPlugin)) + +// new way of registering checks +window.OCA.WorkflowEngine.registerCheck({ + class: 'OCA\\WorkflowEngine\\Check\\FileMimeType', + name: t('workflowengine', 'File MIME type'), + operators: [ + { operator: 'is', name: t('workflowengine', 'is') }, + { operator: '!is', name: t('workflowengine', 'is not') }, + { operator: 'matches', name: t('workflowengine', 'matches') }, + { operator: '!matches', name: t('workflowengine', 'does not match') } + ], + component: FileMimeType +}) +Vue.use(Vuex) Vue.prototype.t = t + const View = Vue.extend(Settings) new View({ store From ae55829989d84c6e3937982479bacb79576333cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 30 Aug 2019 16:18:19 +0200 Subject: [PATCH 49/68] Document plugins to be used by integrators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- .../src/components/Values/file.js | 67 ++++++++++++++++ apps/workflowengine/src/workflowengine.js | 77 +++++++++++-------- 2 files changed, 112 insertions(+), 32 deletions(-) create mode 100644 apps/workflowengine/src/components/Values/file.js diff --git a/apps/workflowengine/src/components/Values/file.js b/apps/workflowengine/src/components/Values/file.js new file mode 100644 index 00000000000..4a473dbc13c --- /dev/null +++ b/apps/workflowengine/src/components/Values/file.js @@ -0,0 +1,67 @@ +/* + * @copyright Copyright (c) 2019 Julius Härtl + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +import './../../legacy/filenameplugin' +import './../../legacy/filesystemtagsplugin' +import './../../legacy/requestremoteaddressplugin' +import './../../legacy/requesttimeplugin' +import './../../legacy/requesturlplugin' +import './../../legacy/requestuseragentplugin' +import './../../legacy/usergroupmembershipplugin' + +import FileMimeType from './FileMimeType'; +import SizeValue from './SizeValue'; + +const FileChecks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => { + if (plugin.component) { + return { ...plugin.getCheck(), component: plugin.component() } + } + return plugin.getCheck() +}) + + +// new way of registering checks +FileChecks.push({ + class: 'OCA\\WorkflowEngine\\Check\\FileMimeType', + name: t('workflowengine', 'File MIME type'), + operators: [ + { operator: 'is', name: t('workflowengine', 'is') }, + { operator: '!is', name: t('workflowengine', 'is not') }, + { operator: 'matches', name: t('workflowengine', 'matches') }, + { operator: '!matches', name: t('workflowengine', 'does not match') } + ], + component: FileMimeType +}) + +FileChecks.push({ + class: 'OCA\\WorkflowEngine\\Check\\FileSize', + name: t('workflowengine', 'File size (upload)'), + operators: [ + { operator: 'less', name: t('workflowengine', 'less') }, + { operator: '!greater', name: t('workflowengine', 'less or equals') }, + { operator: '!less', name: t('workflowengine', 'greater or equals') }, + { operator: 'greater', name: t('workflowengine', 'greater') } + ], + component: SizeValue +}) + +export default FileChecks diff --git a/apps/workflowengine/src/workflowengine.js b/apps/workflowengine/src/workflowengine.js index bdf30397883..6e22852b829 100644 --- a/apps/workflowengine/src/workflowengine.js +++ b/apps/workflowengine/src/workflowengine.js @@ -1,50 +1,63 @@ -import './legacy/filemimetypeplugin' -import './legacy/filenameplugin' -import './legacy/filesizeplugin' -import './legacy/filesystemtagsplugin' -import './legacy/requestremoteaddressplugin' -import './legacy/requesttimeplugin' -import './legacy/requesturlplugin' -import './legacy/requestuseragentplugin' -import './legacy/usergroupmembershipplugin' - import Vue from 'vue' import Vuex from 'vuex' - import store from './store' import Settings from './components/Workflow' -import FileMimeType from './components/Values/FileMimeType'; +import FileValues from './components/Values/file' + +/** + * A plugin for displaying a custom value field for checks + * + * @typedef {Object} CheckPlugin + * @property {string} class - The PHP class name of the check + * @property {Comparison[]} operators - A list of possible comparison operations running on the check + * @property {Vue} component - A vue component to handle the rendering of options + * The component should handle the v-model directive properly, + * so it needs a value property to receive data and emit an input + * event once the data has changed + **/ + +/** + * A plugin for extending the admin page repesentation of a operator + * + * @typedef {Object} OperatorPlugin + * @property {string} class - The PHP class name of the check + * @property {string} operation - Default value for the operation field + * @property {string} color - Custom color code to be applied for the operator selector + * @property {Vue} component - A vue component to handle the rendering of options + * The component should handle the v-model directive properly, + * so it needs a value property to receive data and emit an input + * event once the data has changed + */ +/** + * @typedef {Object} Comparison + * @property {string} operator - value the comparison should have, e.g. !less, greater + * @property {string} name - Translated readable text, e.g. less or equals + **/ + +/** + * Public javascript api for apps to register custom plugins + */ window.OCA.WorkflowEngine = Object.assign({}, OCA.WorkflowEngine, { + /** + * + * @param {CheckPlugin} Plugin + */ registerCheck: function (Plugin) { store.commit('addPluginCheck', Plugin) }, + /** + * + * @param {OperatorPlugin} Plugin + */ registerOperator: function (Plugin) { store.commit('addPluginOperator', Plugin) } }) -// Load legacy plugins for now and register them in the new plugin system -Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => { - if (plugin.component) { - return { ...plugin.getCheck(), component: plugin.component() } - } - return plugin.getCheck() -}).forEach((legacyCheckPlugin) => window.OCA.WorkflowEngine.registerCheck(legacyCheckPlugin)) - -// new way of registering checks -window.OCA.WorkflowEngine.registerCheck({ - class: 'OCA\\WorkflowEngine\\Check\\FileMimeType', - name: t('workflowengine', 'File MIME type'), - operators: [ - { operator: 'is', name: t('workflowengine', 'is') }, - { operator: '!is', name: t('workflowengine', 'is not') }, - { operator: 'matches', name: t('workflowengine', 'matches') }, - { operator: '!matches', name: t('workflowengine', 'does not match') } - ], - component: FileMimeType -}) +// Register shipped checks for file entity +FileValues.forEach((checkPlugin) => window.OCA.WorkflowEngine.registerCheck(checkPlugin)) Vue.use(Vuex) Vue.prototype.t = t From 28a7721b2b485e2ad842d91001601e5e35f71b70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Sat, 31 Aug 2019 11:53:15 +0200 Subject: [PATCH 50/68] Handle operator registration properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/workflowengine/src/components/Check.vue | 3 +- apps/workflowengine/src/components/Event.vue | 16 +++++++- .../src/components/Operation.vue | 1 + apps/workflowengine/src/components/Rule.vue | 39 ++++++++++++++----- .../src/components/Values/SizeValue.vue | 19 ++++++++- apps/workflowengine/src/services/Operation.js | 30 +------------- apps/workflowengine/src/store.js | 13 ++++--- apps/workflowengine/src/workflowengine.js | 6 ++- 8 files changed, 78 insertions(+), 49 deletions(-) diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue index 5f8140f2222..8583288016f 100644 --- a/apps/workflowengine/src/components/Check.vue +++ b/apps/workflowengine/src/components/Check.vue @@ -89,11 +89,12 @@ export default { width: 100%; padding-right: 20px; & > *:not(.icon-delete) { - width: 200px; + width: 180px; } & > .multiselect, & > input[type=text] { margin-right: 5px; + margin-bottom: 5px; } } input[type=text] { diff --git a/apps/workflowengine/src/components/Event.vue b/apps/workflowengine/src/components/Event.vue index 2621b8ca3ba..5ff59882b9a 100644 --- a/apps/workflowengine/src/components/Event.vue +++ b/apps/workflowengine/src/components/Event.vue @@ -1,6 +1,6 @@ - - diff --git a/apps/workflowengine/src/components/Values/file.js b/apps/workflowengine/src/components/Values/file.js deleted file mode 100644 index 2d8ca936391..00000000000 --- a/apps/workflowengine/src/components/Values/file.js +++ /dev/null @@ -1,133 +0,0 @@ -/* - * @copyright Copyright (c) 2019 Julius Härtl - * - * @author Julius Härtl - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -import './../../legacy/filesystemtagsplugin' -import './../../legacy/requesttimeplugin' -import './../../legacy/requesturlplugin' -import './../../legacy/requestuseragentplugin' -import './../../legacy/usergroupmembershipplugin' - -import FileMimeType from './FileMimeType'; - -const FileChecks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => { - if (plugin.component) { - return { ...plugin.getCheck(), component: plugin.component() } - } - return plugin.getCheck() -}) - - -// new way of registering checks - -const validateRegex = function(string) { - var regexRegex = /^\/(.*)\/([gui]{0,3})$/ - var result = regexRegex.exec(string) - return result !== null -} - -const validateIPv4 = function(string) { - var regexRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[1-2][0-9]|[1-9])$/ - var result = regexRegex.exec(string) - return result !== null -} - -const validateIPv6 = function(string) { - var regexRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9])$/ - var result = regexRegex.exec(string) - return result !== null -} - -const stringValidator = (check) => { - if (check.operator === 'matches' || check.operator === '!matches') { - return validateRegex(check.value) - } - return true -} - - -FileChecks.push({ - class: 'OCA\\WorkflowEngine\\Check\\FileName', - name: t('workflowengine', 'File name'), - operators: [ - { operator: 'is', name: t('workflowengine', 'is') }, - { operator: '!is', name: t('workflowengine', 'is not') }, - { operator: 'matches', name: t('workflowengine', 'matches') }, - { operator: '!matches', name: t('workflowengine', 'does not match') } - ], - placeholder: (check) => { - if (check.operator === 'matches' || check.operator === '!matches') { - return '/^dummy-.+$/i' - } - return 'filename.txt' - }, - validate: stringValidator -}) - -FileChecks.push({ - class: 'OCA\\WorkflowEngine\\Check\\FileMimeType', - name: t('workflowengine', 'File MIME type'), - operators: [ - { operator: 'is', name: t('workflowengine', 'is') }, - { operator: '!is', name: t('workflowengine', 'is not') }, - { operator: 'matches', name: t('workflowengine', 'matches') }, - { operator: '!matches', name: t('workflowengine', 'does not match') } - ], - component: FileMimeType -}) - -FileChecks.push({ - class: 'OCA\\WorkflowEngine\\Check\\FileSize', - name: t('workflowengine', 'File size (upload)'), - operators: [ - { operator: 'less', name: t('workflowengine', 'less') }, - { operator: '!greater', name: t('workflowengine', 'less or equals') }, - { operator: '!less', name: t('workflowengine', 'greater or equals') }, - { operator: 'greater', name: t('workflowengine', 'greater') } - ], - placeholder: (check) => '5 MB', - validate: (check) => check.value.match(/^[0-9]+[ ]?[kmgt]?b$/i) !== null -}) - -FileChecks.push({ - class: 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress', - name: t('workflowengine', 'Request remote address'), - operators: [ - { operator: 'matchesIPv4', name: t('workflowengine', 'matches IPv4') }, - { operator: '!matchesIPv4', name: t('workflowengine', 'does not match IPv4') }, - { operator: 'matchesIPv6', name: t('workflowengine', 'matches IPv6') }, - { operator: '!matchesIPv6', name: t('workflowengine', 'does not match IPv6') } - ], - placeholder: (check) => { - if (check.operator === 'matchesIPv6' || check.operator === '!matchesIPv6') { - return '::1/128'; - } - return '127.0.0.1/32' - }, - validate: (check) => { - if (check.operator === 'matchesIPv6' || check.operator === '!matchesIPv6') { - return validateIPv6(check.value) - } - return validateIPv4(check.value) - } -}) - -export default FileChecks diff --git a/apps/workflowengine/src/helpers/validators.js b/apps/workflowengine/src/helpers/validators.js new file mode 100644 index 00000000000..033ce2ec7fe --- /dev/null +++ b/apps/workflowengine/src/helpers/validators.js @@ -0,0 +1,49 @@ +/* + * @copyright Copyright (c) 2019 Julius Härtl + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +const validateRegex = function(string) { + var regexRegex = /^\/(.*)\/([gui]{0,3})$/ + var result = regexRegex.exec(string) + return result !== null +} + +const validateIPv4 = function(string) { + var regexRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[1-2][0-9]|[1-9])$/ + var result = regexRegex.exec(string) + return result !== null +} + +const validateIPv6 = function(string) { + var regexRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9])$/ + var result = regexRegex.exec(string) + return result !== null +} + +const stringValidator = (check) => { + if (check.operator === 'matches' || check.operator === '!matches') { + return validateRegex(check.value) + } + return true +} + +export { validateRegex, stringValidator, validateIPv4, validateIPv6 } diff --git a/apps/workflowengine/src/workflowengine.js b/apps/workflowengine/src/workflowengine.js index e05ac0a5053..149f9d4aa85 100644 --- a/apps/workflowengine/src/workflowengine.js +++ b/apps/workflowengine/src/workflowengine.js @@ -1,9 +1,8 @@ import Vue from 'vue' import Vuex from 'vuex' import store from './store' - import Settings from './components/Workflow' -import FileValues from './components/Values/file' +import ShippedChecks from './components/Checks' /** * A plugin for displaying a custom value field for checks @@ -43,7 +42,6 @@ import FileValues from './components/Values/file' */ window.OCA.WorkflowEngine = Object.assign({}, OCA.WorkflowEngine, { - /** * * @param {CheckPlugin} Plugin @@ -60,8 +58,8 @@ window.OCA.WorkflowEngine = Object.assign({}, OCA.WorkflowEngine, { } }) -// Register shipped checks for file entity -FileValues.forEach((checkPlugin) => window.OCA.WorkflowEngine.registerCheck(checkPlugin)) +// Register shipped checks +ShippedChecks.forEach((checkPlugin) => window.OCA.WorkflowEngine.registerCheck(checkPlugin)) /** * FIXME: remove before merge as this is for UI testing only @@ -69,16 +67,16 @@ FileValues.forEach((checkPlugin) => window.OCA.WorkflowEngine.registerCheck(chec const demo = [ { id: 'OCA\\TestExample\\Operation1', - name: 'Rename file', - description: '🚧 For UI mocking only', - iconClass: 'icon-address-white', + name: 'Convert to PDF', + description: 'Convert a file to PDF using Libreoffice', + iconClass: 'icon-convert-pdf', color: 'var(--color-success)', operation: 'deny' }, { id: 'OCA\\TestExample\\Operation2', name: 'Notify me', - description: '🚧 For UI mocking only', + description: 'Send a Nextcloud Notification', iconClass: 'icon-comment-white', color: 'var(--color-warning)', operation: 'deny' From 0b6706225b27a9953868fdd8d2d3344c0b71048b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 6 Sep 2019 16:24:21 +0200 Subject: [PATCH 58/68] Add moment-timezone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- package-lock.json | 8 ++++++++ package.json | 1 + 2 files changed, 9 insertions(+) diff --git a/package-lock.json b/package-lock.json index 157f490c41b..ac5f79c864c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5719,6 +5719,14 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, + "moment-timezone": { + "version": "0.5.26", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.26.tgz", + "integrity": "sha512-sFP4cgEKTCymBBKgoxZjYzlSovC20Y6J7y3nanDc5RoBIXKlZhoYwBoZGe3flwU6A372AcRwScH8KiwV6zjy1g==", + "requires": { + "moment": ">= 2.9.0" + } + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/package.json b/package.json index d02dd259982..09d0185e516 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "lodash": "^4.17.15", "marked": "^0.7.0", "moment": "^2.24.0", + "moment-timezone": "^0.5.26", "nextcloud-axios": "^0.2.1", "nextcloud-password-confirmation": "^0.4.2", "nextcloud-vue": "^0.12.3", From 24aec9b9d27378ab19ebe028f46f26bcf0a1b901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Mon, 9 Sep 2019 13:53:03 +0200 Subject: [PATCH 59/68] Frontend polishing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/workflowengine/src/components/Check.vue | 4 ++ .../src/components/Checks/RequestTime.vue | 2 +- .../src/components/Checks/request.js | 4 +- .../src/components/Operation.vue | 17 +++++-- .../components/Operations/ConvertToPdf.vue | 46 ------------------- .../src/components/Operations/Tag.vue | 31 ------------- apps/workflowengine/src/components/Rule.vue | 10 ++-- .../src/components/Workflow.vue | 6 +-- apps/workflowengine/src/workflowengine.js | 23 ---------- apps/workflowengine/webpack.js | 12 ----- 10 files changed, 27 insertions(+), 128 deletions(-) delete mode 100644 apps/workflowengine/src/components/Operations/ConvertToPdf.vue delete mode 100644 apps/workflowengine/src/components/Operations/Tag.vue diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue index 508b8a4a1f4..8f7fbca2865 100644 --- a/apps/workflowengine/src/components/Check.vue +++ b/apps/workflowengine/src/components/Check.vue @@ -138,6 +138,10 @@ export default { margin-top: -5px; margin-bottom: -5px; } + button.action-item.action-item--single.icon-delete { + height: 34px; + width: 34px; + } .invalid { border: 1px solid var(--color-error) !important; } diff --git a/apps/workflowengine/src/components/Checks/RequestTime.vue b/apps/workflowengine/src/components/Checks/RequestTime.vue index 2f09693232a..9ea211874fe 100644 --- a/apps/workflowengine/src/components/Checks/RequestTime.vue +++ b/apps/workflowengine/src/components/Checks/RequestTime.vue @@ -74,7 +74,7 @@ export default { display: flex; flex-grow: 1; flex-wrap: wrap; - max-width: 200px; + max-width: 180px; .multiselect { width: 100%; diff --git a/apps/workflowengine/src/components/Checks/request.js b/apps/workflowengine/src/components/Checks/request.js index d2e4eaa3565..8b36b89a9e8 100644 --- a/apps/workflowengine/src/components/Checks/request.js +++ b/apps/workflowengine/src/components/Checks/request.js @@ -42,7 +42,6 @@ const RequestChecks = [ { operator: 'in', name: t('workflowengine', 'between') }, { operator: '!in', name: t('workflowengine', 'not between') } ], - // TODO: implement component component: RequestTime }, { @@ -54,7 +53,8 @@ const RequestChecks = [ { operator: 'matches', name: t('workflowengine', 'matches') }, { operator: '!matches', name: t('workflowengine', 'does not match') } ], - component: RequestUserAgent + // TODO: implement component + // component: RequestUserAgent }, { class: 'OCA\\WorkflowEngine\\Check\\UserGroupMembership', diff --git a/apps/workflowengine/src/components/Operation.vue b/apps/workflowengine/src/components/Operation.vue index 45b0f24223d..3cd7378f0df 100644 --- a/apps/workflowengine/src/components/Operation.vue +++ b/apps/workflowengine/src/components/Operation.vue @@ -5,7 +5,9 @@

{{ operation.name }}

{{ operation.description }} - +
+ +
@@ -28,13 +30,14 @@ export default { diff --git a/apps/workflowengine/src/components/Operations/ConvertToPdf.vue b/apps/workflowengine/src/components/Operations/ConvertToPdf.vue deleted file mode 100644 index 5fa1676e092..00000000000 --- a/apps/workflowengine/src/components/Operations/ConvertToPdf.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - - - diff --git a/apps/workflowengine/src/components/Operations/Tag.vue b/apps/workflowengine/src/components/Operations/Tag.vue deleted file mode 100644 index 7a27e2b572d..00000000000 --- a/apps/workflowengine/src/components/Operations/Tag.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - - - diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue index 82e19dbe82b..c5c6094879a 100644 --- a/apps/workflowengine/src/components/Rule.vue +++ b/apps/workflowengine/src/components/Rule.vue @@ -1,6 +1,5 @@ - + diff --git a/apps/workflowengine/src/components/Checks/FileSystemTag.vue b/apps/workflowengine/src/components/Checks/FileSystemTag.vue index ead4edf60c2..2875b64d48e 100644 --- a/apps/workflowengine/src/components/Checks/FileSystemTag.vue +++ b/apps/workflowengine/src/components/Checks/FileSystemTag.vue @@ -22,59 +22,59 @@ diff --git a/apps/workflowengine/src/components/Checks/file.js b/apps/workflowengine/src/components/Checks/file.js index 431a3f93580..80bd120079f 100644 --- a/apps/workflowengine/src/components/Checks/file.js +++ b/apps/workflowengine/src/components/Checks/file.js @@ -22,7 +22,7 @@ import FileMimeType from './FileMimeType' import { stringValidator, validateIPv4, validateIPv6 } from './../../helpers/validators' -import FileSystemTag from './FileSystemTag'; +import FileSystemTag from './FileSystemTag' const FileChecks = [ { class: 'OCA\\WorkflowEngine\\Check\\FileName', diff --git a/apps/workflowengine/src/components/Checks/request.js b/apps/workflowengine/src/components/Checks/request.js index ee574f9de90..1059bf45b5a 100644 --- a/apps/workflowengine/src/components/Checks/request.js +++ b/apps/workflowengine/src/components/Checks/request.js @@ -23,6 +23,7 @@ import RequestUserAgent from './RequestUserAgent' import RequestTime from './RequestTime' import RequestURL from './RequestURL' +import RequestUserGroup from './RequestUserGroup' const RequestChecks = [ { @@ -62,8 +63,8 @@ const RequestChecks = [ operators: [ { operator: 'is', name: t('workflowengine', 'is member of') }, { operator: '!is', name: t('workflowengine', 'is not member of') } - ] - // TODO: implement component + ], + component: RequestUserGroup } ] diff --git a/apps/workflowengine/src/css/multiselect.css b/apps/workflowengine/src/css/multiselect.css new file mode 100644 index 00000000000..8eb7583744b --- /dev/null +++ b/apps/workflowengine/src/css/multiselect.css @@ -0,0 +1,11 @@ +.multiselect::v-deep .multiselect__single { + display: flex; +} + +.option__icon { + min-width: 25px; +} + +input, .multiselect { + width: 100%; +} diff --git a/apps/workflowengine/src/mixins/valueMixin.js b/apps/workflowengine/src/mixins/valueMixin.js new file mode 100644 index 00000000000..e6ea5bbdcf4 --- /dev/null +++ b/apps/workflowengine/src/mixins/valueMixin.js @@ -0,0 +1,54 @@ +/* + * @copyright Copyright (c) 2019 Julius Härtl + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +const valueMixin = { + props: { + value: { + type: String, + default: '' + }, + check: { + type: Object, + default: () => { return {} } + } + }, + data() { + return { + newValue: '' + } + }, + watch: { + value: { + immediate: true, + handler: function(value) { + this.updateInternalValue(value) + } + } + }, + methods: { + updateInternalValue(value) { + this.newValue = value + } + } +} + +export default valueMixin From 9e0078824e1303c78575c4287ce3f228659d8ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Mon, 9 Sep 2019 16:55:36 +0200 Subject: [PATCH 64/68] Add nextcloud-router MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- package-lock.json | 328 ++++++++-------------------------------------- package.json | 1 + 2 files changed, 55 insertions(+), 274 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac5f79c864c..3681b527582 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", "dev": true, "requires": { "@babel/highlight": "^7.0.0" @@ -35,73 +35,12 @@ "source-map": "^0.5.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.0.tgz", - "integrity": "sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA==", - "dev": true, - "requires": { - "@babel/types": "^7.6.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, "@babel/parser": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz", "integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==", "dev": true }, - "@babel/template": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", - "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.6.0", - "@babel/types": "^7.6.0" - } - }, - "@babel/traverse": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.0.tgz", - "integrity": "sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.6.0", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.6.0", - "@babel/types": "^7.6.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -132,17 +71,6 @@ "trim-right": "^1.0.1" }, "dependencies": { - "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -190,19 +118,6 @@ "@babel/helper-function-name": "^7.1.0", "@babel/types": "^7.5.5", "lodash": "^4.17.13" - }, - "dependencies": { - "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-explode-assignable-expression": { @@ -251,19 +166,6 @@ "dev": true, "requires": { "@babel/types": "^7.5.5" - }, - "dependencies": { - "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-module-imports": { @@ -287,19 +189,6 @@ "@babel/template": "^7.4.4", "@babel/types": "^7.5.5", "lodash": "^4.17.13" - }, - "dependencies": { - "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-optimise-call-expression": { @@ -349,19 +238,6 @@ "@babel/helper-optimise-call-expression": "^7.0.0", "@babel/traverse": "^7.5.5", "@babel/types": "^7.5.5" - }, - "dependencies": { - "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-simple-access": { @@ -404,92 +280,6 @@ "@babel/template": "^7.6.0", "@babel/traverse": "^7.6.0", "@babel/types": "^7.6.0" - }, - "dependencies": { - "@babel/generator": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.0.tgz", - "integrity": "sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA==", - "dev": true, - "requires": { - "@babel/types": "^7.6.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/parser": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz", - "integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==", - "dev": true - }, - "@babel/template": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", - "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.6.0", - "@babel/types": "^7.6.0" - } - }, - "@babel/traverse": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.0.tgz", - "integrity": "sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.6.0", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.6.0", - "@babel/types": "^7.6.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - } - } - }, - "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } } }, "@babel/highlight": { @@ -988,30 +778,25 @@ "invariant": "^2.2.2", "js-levenshtein": "^1.1.3", "semver": "^5.5.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", - "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", + "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.4.4", - "@babel/types": "^7.4.4" + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.0" + }, + "dependencies": { + "@babel/parser": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz", + "integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==", + "dev": true + } } }, "@babel/traverse": { @@ -1031,32 +816,12 @@ "lodash": "^4.17.13" }, "dependencies": { - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, "@babel/parser": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz", "integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==", "dev": true }, - "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -1069,13 +834,13 @@ } }, "@babel/types": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.0.tgz", - "integrity": "sha512-UFpDVqRABKsW01bvw7/wSUe56uy6RXM5+VJibVVAybDGxEW25jdwiFJEf7ASvSaC7sN7rbE/l3cLp2izav+CtQ==", + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", "dev": true, "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" } }, @@ -3048,9 +2813,9 @@ } }, "electron-to-chromium": { - "version": "1.3.252", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.252.tgz", - "integrity": "sha512-NWJ5TztDnjExFISZHFwpoJjMbLUifsNBnx7u2JI0gCw6SbKyQYYWWtBHasO/jPtHym69F4EZuTpRNGN11MT/jg==", + "version": "1.3.254", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.254.tgz", + "integrity": "sha512-7I5/OkgR6JKy6RFLJeru0kc0RMmmMu1UnkHBKInFKRrg1/4EQKIqOaUqITSww/SZ1LqWwp1qc/LLoIGy449eYw==", "dev": true }, "elliptic": { @@ -3178,9 +2943,9 @@ "dev": true }, "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "eventemitter3": { @@ -3549,9 +3314,9 @@ } }, "follow-redirects": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", - "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.8.1.tgz", + "integrity": "sha512-micCIbldHioIegeKs41DoH0KS3AXfFzgS30qVkM6z/XOE/GJgvmsoc839NUqa1B9udYe9dQxgv7KFwng6+p/dw==", "requires": { "debug": "^3.0.0" } @@ -5651,9 +5416,9 @@ } }, "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mississippi": { "version": "3.0.0", @@ -5790,6 +5555,21 @@ "resolved": "https://registry.npmjs.org/nextcloud-password-confirmation/-/nextcloud-password-confirmation-0.4.2.tgz", "integrity": "sha512-DZXsfdk3iCsRWtd0lsYM1nqQ/oD9YlQ2WbC4qRZo20enUQLjJWZ8lYhKftXowmYL41t7spThnznJ7ihMG2/vUQ==" }, + "nextcloud-router": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/nextcloud-router/-/nextcloud-router-0.0.9.tgz", + "integrity": "sha512-w0i4xqFwJJuXNWFf9AB9huCWW5XmwdJHSHa7oXlOLTAvP9WxwU3KCm/mcKy8Eb0cT0ElRPg72HLUxl7oyEWoBQ==", + "requires": { + "core-js": "^3.1.4" + }, + "dependencies": { + "core-js": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz", + "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==" + } + } + }, "nextcloud-vue": { "version": "0.12.3", "resolved": "https://registry.npmjs.org/nextcloud-vue/-/nextcloud-vue-0.12.3.tgz", @@ -5936,9 +5716,9 @@ } }, "node-releases": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.29.tgz", - "integrity": "sha512-R5bDhzh6I+tpi/9i2hrrvGJ3yKPYzlVOORDkXhnZuwi5D3q1I5w4vYy24PJXTcLk9Q0kws9TO77T75bcK8/ysQ==", + "version": "1.1.30", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.30.tgz", + "integrity": "sha512-BHcr1g6NeUH12IL+X3Flvs4IOnl1TL0JczUhEZjDE+FXXPQcVCNr8NEPb01zqGxzhTpdyJL5GXemaCW7aw6Khw==", "dev": true, "requires": { "semver": "^5.3.0" @@ -6444,9 +6224,9 @@ "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==" }, "portfinder": { - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.24.tgz", - "integrity": "sha512-ekRl7zD2qxYndYflwiryJwMioBI7LI7rVXg3EnLK3sjkouT5eOuhS3gS255XxBksa30VG8UPZYZCdgfGOfkSUg==", + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.23.tgz", + "integrity": "sha512-B729mL/uLklxtxuiJKfQ84WPxNw5a7Yhx3geQZdcA4GjNjZSTSSMMWyoennMVnTWSmAR0lMdzWYN0JLnHrg1KQ==", "requires": { "async": "^1.5.2", "debug": "^2.2.0", @@ -9284,7 +9064,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { diff --git a/package.json b/package.json index 09d0185e516..022db976dab 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "moment-timezone": "^0.5.26", "nextcloud-axios": "^0.2.1", "nextcloud-password-confirmation": "^0.4.2", + "nextcloud-router": "0.0.9", "nextcloud-vue": "^0.12.3", "nextcloud-vue-collections": "^0.5.6", "query-string": "^5.1.1", From 7683208dfa4cf5f0ff196ee0cabdacf8046592eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 10 Sep 2019 09:44:20 +0200 Subject: [PATCH 65/68] Workflow vue cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/workflowengine/src/components/Check.vue | 4 ++-- .../src/components/Checks/FileSystemTag.vue | 11 +---------- .../Checks/MultiselectTag/MultiselectTag.vue | 11 ++++------- .../src/components/Checks/RequestTime.vue | 6 +----- .../src/components/Checks/RequestURL.vue | 17 +++++++++-------- .../src/components/Checks/RequestUserAgent.vue | 17 +++++++++-------- .../src/components/Checks/file.js | 3 ++- 7 files changed, 28 insertions(+), 41 deletions(-) diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue index 905c2c989b1..06667b1a7ee 100644 --- a/apps/workflowengine/src/components/Check.vue +++ b/apps/workflowengine/src/components/Check.vue @@ -14,8 +14,8 @@ - - + + diff --git a/apps/workflowengine/src/components/Checks/FileSystemTag.vue b/apps/workflowengine/src/components/Checks/FileSystemTag.vue index 2875b64d48e..e2f66b30a4b 100644 --- a/apps/workflowengine/src/components/Checks/FileSystemTag.vue +++ b/apps/workflowengine/src/components/Checks/FileSystemTag.vue @@ -42,7 +42,6 @@ export default { }, data() { return { - valid: false, newValue: [] } }, @@ -62,16 +61,8 @@ export default { this.newValue = null } }, - validate() { - return true - }, update() { - if (this.validate()) { - this.$emit('input', this.newValue || '') - this.valid = false - } else { - this.valid = false - } + this.$emit('input', this.newValue || '') } } } diff --git a/apps/workflowengine/src/components/Checks/MultiselectTag/MultiselectTag.vue b/apps/workflowengine/src/components/Checks/MultiselectTag/MultiselectTag.vue index a046861b132..88b56a1d4e9 100644 --- a/apps/workflowengine/src/components/Checks/MultiselectTag/MultiselectTag.vue +++ b/apps/workflowengine/src/components/Checks/MultiselectTag/MultiselectTag.vue @@ -28,8 +28,7 @@ :custom-label="tagLabel" class="multiselect-vue" :multiple="multiple" :close-on-select="false" :tag-width="60" - :disabled="disabled" @input="update" - @search-change="asyncFind"> + :disabled="disabled" @input="update"> {{ t('core', 'No results') }}